암호화 encryption
암호화 인증 모듈을 쉽게쓰기 위한 코덱 의존성 추가
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
Spring Security crpto 사용을 위한 의존성 추가
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.0.2</version>
</dependency>
SevletConfig - BCrypt를 이용하여 Encoding하기 위해 @Bean으로 객체 생성
package com.bit.boot09.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc
public class ServletConfig extends WebMvcConfigurerAdapter{
@Bean
PasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
CryptService
package com.bit.boot09.service;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class CryptService {
// 앞서 생성한 Bcrypt 객체 주입
final PasswordEncoder passwordEncoder;
// @Value("${secure.key}") properties에 저장해둔 것을 불러올 수 있다.
String key="oingisprettyintheworld1234567890";
private String iv = "0123456789abcdef"; // 16byte
// 암호화
// 단방향 - 복호화(decoding) 불가능 - 패스워드 => MD5 SHA256
public String createMd5Encrypt(String msg) {
return DigestUtils.md5Hex(msg);
}
public String createSha256Encrypt(String msg) {
return DigestUtils.sha256Hex(msg);
}
public String createSha512Encrypt(String msg) {
return DigestUtils.sha3_512Hex(msg);
}
// 양방향 - 복호화 가능 단, 키를 이용해 인증해야만 가능 =>
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
public String createAESEncrypt(String msg) throws Exception {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes()));
byte[] encrypted = c.doFinal(msg.getBytes("UTF-8"));
String enStr = new String(Base64.encodeBase64(encrypted));
return enStr;
}
public String createAESDecrypt(String msg) throws Exception {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes()));
byte[] byteStr = Base64.decodeBase64(msg.getBytes());
return new String(c.doFinal(byteStr), "UTF-8");
}
// spring security crpto
public String springEcrypt(String msg) {
return passwordEncoder.encode(msg);
}
public boolean isMatches(String msg, String result) {
return passwordEncoder.matches(msg, result);
}
}
CryptServiceTest
package com.bit.boot09.service;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CryptServiceTest {
String msg;
@Autowired
CryptService cryptService;
@BeforeEach
void setUp() throws Exception {
msg = "abcd1234";
}
@Test
void test() {
System.out.println(cryptService.createMd5Encrypt(msg));
System.out.println(cryptService.createSha256Encrypt(msg));
System.out.println(cryptService.createSha512Encrypt(msg));
}
@Test
void test02() throws Exception {
String result1 = cryptService.createAESEncrypt(msg);
System.out.println(result1);
String result2 = cryptService.createAESDecrypt(result1);
System.out.println(result2);
}
@Test
void test03() {
String result = cryptService.springEcrypt(msg);
System.out.println(result);
System.out.println(cryptService.isMatches(msg,result));
}
}
Token
JWT
의존성 추가
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.4</version>
</dependency>
jwtService
package com.bit.boot09.service;
import org.springframework.stereotype.Service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
@Service
public class JwtService {
String secretKey = "abcdefg";
// Token 인증 -> 완료되면 user 값 리턴
public String verify(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
JWTVerifier verifier = JWT.require(algorithm)
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("user").asString();
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
return null;
}
// access token 생성
public String createHs256() {
try {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
String token = JWT.create()
.withIssuer("auth0")
.withClaim("user", "user02")
.sign(algorithm);
return token;
} catch (JWTCreationException exception){
return null;
}
}
}
HomeController
package com.bit.boot09.comtroller;
import java.net.HttpCookie;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bit.boot09.model.ResponseDeptVo;
import com.bit.boot09.service.JwtService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class HomeController {
final JwtService jwtService;
@GetMapping
public String index() {
return "index";
}
@GetMapping("/join")
public String join() {
return jwtService.createHs256();
}
@GetMapping("/join2")
public String join2(HttpServletResponse resp) {
// JWT token을 생성하여 cookie에 저장
String value = "Bearer "+jwtService.createHs256();
value = Base64.encodeBase64String(value.getBytes());
Cookie cookie = new Cookie("authorization", value);
resp.addCookie(cookie);
return "<h1>쿠키에 등록되었습니다.</h1>";
}
@GetMapping("/cookie")
public String cookie(HttpServletRequest req) {
// 쿠키에 저장된 token 불러오기
Cookie cookie = req.getCookies()[0];
String value = cookie.getValue();
value = new String(Base64.decodeBase64(value));
return value;
}
@GetMapping("/api/dept")
public List<ResponseDeptVo> list() {
return List.of(ResponseDeptVo.builder().deptno(1111).dname("tester1").loc("test").build(),
ResponseDeptVo.builder().deptno(2222).dname("tester2").loc("test").build(),
ResponseDeptVo.builder().deptno(3333).dname("tester3").loc("test").build(),
ResponseDeptVo.builder().deptno(4444).dname("tester4").loc("test").build());
}
}
ServletConfig
package com.bit.boot09.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.bit.boot09.comtroller.DeptControllerIntercepter;
import com.bit.boot09.service.JwtService;
import lombok.AllArgsConstructor;
@Configuration
@EnableWebMvc
@AllArgsConstructor
public class ServletConfig extends WebMvcConfigurerAdapter{
final JwtService jwtService;
@Bean
PasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// 컨트롤러 호출 전에 실행되는 DeptControllerInterceptor 추가
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DeptControllerIntercepter(jwtService));
}
}
DeptControllerInterceptor
package com.bit.boot09.comtroller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import com.bit.boot09.service.JwtService;
public class DeptControllerIntercepter implements HandlerInterceptor{
private JwtService jwtService;
public DeptControllerIntercepter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 특정 URI에서 token 검증됨
if(request.getRequestURI().toString().startsWith("/api/")) {
// System.out.println("HandlerInterceptor...");
// Authorization에 값이 들어 가 있는지
String authorization = request.getHeader("authorization");
if(authorization == null) {
response.sendRedirect("/login");
return false;
}
// Bearer [token Value] 형태로 저장되어있기 때문에 스플릿
String token = authorization.split(" ")[1];
// JWT token이 유효한 지 검사
String user = jwtService.verify(token);
System.out.println(user);
if(user==null) {
response.sendRedirect("/login");
return false;
}
}
return true;
}
}
Spring Security
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
SecurityConfig
package com.bit.boot10.config;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests)-> requests
.antMatchers("/","/ex01", "login").permitAll() // 매치되는 uri는 무조건 허용
.anyRequest().authenticated() // 외의 request는 로그인 필요
)
.formLogin((form)->form.loginPage("/login").permitAll()) // 로그인 페이지 지정 + 무조건 허용
.logout((logout)->logout.logoutSuccessUrl("/").permitAll()); // 로그아웃 후 이동할 url 지정 + 무조건 허용
http.csrf().disable(); // csrf 체크를 해야하는데 테스트를 위해서 비사용 처리
return http.build();
}
// @Bean
// UserDetailsService userDetailsService() {
// UserDetails user =
// User.withDefaultPasswordEncoder()
// .username("user01")
// .password("1234")
// .roles("USER")
// .build();
// UserDetails user2 =
// User.withDefaultPasswordEncoder()
// .username("user02")
// .password("4567")
// .roles("USER")
// .build();
// UserDetails user3 =
// User.withDefaultPasswordEncoder()
// .username("user03")
// .password("3456")
// .roles("USER")
// .build();
// return new InMemoryUserDetailsManager(user, user2, user3);
// }
}
Controller
package com.bit.boot10.controller;
import java.util.Arrays;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/ex01")
public String ex01() {
return "ex01";
}
@GetMapping("/ex02")
public String ex02(HttpSession session, @AuthenticationPrincipal User user) {
// @AutenticationPrincipal 을 이용해서 로그인한 유저의 정보 불러올 수 있다.
System.out.println(Arrays.toString(session.getValueNames()));
System.out.println(user.getUsername());
return "ex02";
}
@GetMapping("/ex03")
public String ex03() {
return "ex03";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
UserDetailServiceImpl
package com.bit.boot10.service;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class UserDetailServiceImpl implements UserDetailsService{
// DeptMapper deptMapper;
final PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// JPA // DeptVo bean = findByDname(username);
// Mybatis // DeptVo bean = deptMapper.selectOne(username);
return User.builder()
.username("scott")
.password(passwordEncoder.encode("tiger"))
.roles("USER,ADMIN").build();
}
}