type: Spring
archive: false
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("com.auth0:java-jwt:3.10.3")
}
import com.ssafy.api.service.UserService;
import com.ssafy.common.auth.CustomOAuth2UserService;
import com.ssafy.common.auth.JwtAuthenticationFilter;
import com.ssafy.common.auth.SsafyUserDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* ์ธ์ฆ(authentication) ์ ์ธ๊ฐ(authorization) ์ฒ๋ฆฌ๋ฅผ ์ํ ์คํ๋ง ์ํ๋ฆฌํฐ ์ค์ ์ ์.
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SsafyUserDetailService ssafyUserDetailService;
@Autowired
private UserService userService;
private final CustomOAuth2UserService customOAuth2UserService;
// Password ์ธ์ฝ๋ฉ ๋ฐฉ์์ BCrypt ์ํธํ ๋ฐฉ์ ์ฌ์ฉ
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// DAO ๊ธฐ๋ฐ์ผ๋ก Authentication Provider๋ฅผ ์์ฑ
// BCrypt Password Encoder์ UserDetailService ๊ตฌํ์ฒด๋ฅผ ์ค์
@Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.ssafyUserDetailService);
return daoAuthenticationProvider;
}
// DAO ๊ธฐ๋ฐ์ Authentication Provider๊ฐ ์ ์ฉ๋๋๋ก ์ค์
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ์ด๋ฏ๋ก ์ธ์
์ฌ์ฉ ํ์ง์์
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), userService)) //HTTP ์์ฒญ์ JWT ํ ํฐ ์ธ์ฆ ํํฐ๋ฅผ ๊ฑฐ์น๋๋ก ํํฐ๋ฅผ ์ถ๊ฐ
.authorizeRequests()
.antMatchers("/api/v1/users/me").authenticated() //์ธ์ฆ์ด ํ์ํ URL๊ณผ ํ์ํ์ง ์์ URL์ ๋ํ์ฌ ์ค์
.anyRequest().permitAll()
.and().cors()
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
/**
* ์์ฒญ ํค๋์ jwt ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ, ํ ํฐ ๊ฒ์ฆ ๋ฐ ์ธ์ฆ ์ฒ๋ฆฌ ๋ก์ง ์ ์.
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private UserService userService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, UserService userService) {
super(authenticationManager);
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Read the Authorization header, where the JWT Token should be
String header = request.getHeader(JwtTokenUtil.HEADER_STRING);
// If header does not contain BEARER or is null delegate to Spring impl and exit
if (header == null || !header.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
filterChain.doFilter(request, response);
return;
}
try {
// If header is present, try grab user principal from database and perform authorization
Authentication authentication = getAuthentication(request);
// jwt ํ ํฐ์ผ๋ก ๋ถํฐ ํ๋ํ ์ธ์ฆ ์ ๋ณด(authentication) ์ค์ .
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception ex) {
ResponseBodyWriteUtil.sendError(request, response, ex);
return;
}
filterChain.doFilter(request, response);
}
@Transactional(readOnly = true)
public Authentication getAuthentication(HttpServletRequest request) throws Exception {
String token = request.getHeader(JwtTokenUtil.HEADER_STRING);
// ์์ฒญ ํค๋์ Authorization ํค๊ฐ์ jwt ํ ํฐ์ด ํฌํจ๋ ๊ฒฝ์ฐ์๋ง, ํ ํฐ ๊ฒ์ฆ ๋ฐ ์ธ์ฆ ์ฒ๋ฆฌ ๋ก์ง ์คํ.
if (token != null) {
// parse the token and validate it (decode)
JWTVerifier verifier = JwtTokenUtil.getVerifier();
JwtTokenUtil.handleError(token);
DecodedJWT decodedJWT = verifier.verify(token.replace(JwtTokenUtil.TOKEN_PREFIX, ""));
String email = decodedJWT.getSubject();
// Search in the DB if we find the user by token subject (username)
// If so, then grab user details and create spring auth token using username, pass, authorities/roles
if (email != null) {
// jwt ํ ํฐ์ ํฌํจ๋ ๊ณ์ ์ ๋ณด(userId) ํตํด ์ค์ ๋๋น์ ํด๋น ์ ๋ณด์ ๊ณ์ ์ด ์๋์ง ์กฐํ.
User user = userService.getUserByEmail(email);
if(user != null) {
// ์๋ณ๋ ์ ์ ์ ์ ์ธ ๊ฒฝ์ฐ, ์์ฒญ context ๋ด์์ ์ฐธ์กฐ ๊ฐ๋ฅํ ์ธ์ฆ ์ ๋ณด(jwtAuthentication) ์์ฑ.
SsafyUserDetails userDetails = new SsafyUserDetails(user);
UsernamePasswordAuthenticationToken jwtAuthentication = new UsernamePasswordAuthenticationToken(email,
null, userDetails.getAuthorities());
jwtAuthentication.setDetails(userDetails);
return jwtAuthentication;
}
}
return null;
}
return null;
}
}
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
/**
* jwt ํ ํฐ ์ ํธ ์ ์.
*/
@Component
public class JwtTokenUtil {
private static String secretKey;
private static Integer expirationTime;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String ISSUER = "ssafy.com";
@Autowired
public JwtTokenUtil(@Value("${jwt.secret}") String secretKey, @Value("${jwt.expiration}") Integer expirationTime) {
this.secretKey = secretKey;
this.expirationTime = expirationTime;
}
public void setExpirationTime() {
//JwtTokenUtil.expirationTime = Integer.parseInt(expirationTime);
JwtTokenUtil.expirationTime = expirationTime;
}
public static JWTVerifier getVerifier() {
return JWT
.require(Algorithm.HMAC512(secretKey.getBytes()))
.withIssuer(ISSUER)
.build();
}
public static String getToken(String userId) {
Date expires = JwtTokenUtil.getTokenExpiration(expirationTime);
return JWT.create()
.withSubject(userId)
.withExpiresAt(expires)
.withIssuer(ISSUER)
.withIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
.sign(Algorithm.HMAC512(secretKey.getBytes()));
}
public static String getToken(Instant expires, String userId) {
return JWT.create()
.withSubject(userId)
.withExpiresAt(Date.from(expires))
.withIssuer(ISSUER)
.withIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
.sign(Algorithm.HMAC512(secretKey.getBytes()));
}
public static Date getTokenExpiration(int expirationTime) {
Date now = new Date();
return new Date(now.getTime() + expirationTime);
}
public static void handleError(String token) {
JWTVerifier verifier = JWT
.require(Algorithm.HMAC512(secretKey.getBytes()))
.withIssuer(ISSUER)
.build();
try {
verifier.verify(token.replace(TOKEN_PREFIX, ""));
} catch (AlgorithmMismatchException ex) {
throw ex;
} catch (InvalidClaimException ex) {
throw ex;
} catch (SignatureGenerationException ex) {
throw ex;
} catch (SignatureVerificationException ex) {
throw ex;
} catch (TokenExpiredException ex) {
throw ex;
} catch (JWTCreationException ex) {
throw ex;
} catch (JWTDecodeException ex) {
throw ex;
} catch (JWTVerificationException ex) {
throw ex;
} catch (Exception ex) {
throw ex;
}
}
public static void handleError(JWTVerifier verifier, String token) {
try {
verifier.verify(token.replace(TOKEN_PREFIX, ""));
} catch (AlgorithmMismatchException ex) {
throw ex;
} catch (InvalidClaimException ex) {
throw ex;
} catch (SignatureGenerationException ex) {
throw ex;
} catch (SignatureVerificationException ex) {
throw ex;
} catch (TokenExpiredException ex) {
throw ex;
} catch (JWTCreationException ex) {
throw ex;
} catch (JWTDecodeException ex) {
throw ex;
} catch (JWTVerificationException ex) {
throw ex;
} catch (Exception ex) {
throw ex;
}
}
}