토큰 - 특정 엔드포인트에 접근하려는 클라이언트가 HTTP 헤더를 통해 보내는 문자열
http basic 단점 : 요청할 때마다 자격증명을 보내야 함.
JSON 웹 토큰이란 : 데이터형식이 JSON인 웹 요청
JWT 토큰 세부분으로 구성/ 마침표로 구분
처음 : 헤더(header) : 토큰과 관련된 메타데이터 (알고리즘)
중간 : 본문(payload) : 세부정보 저장(길이제한 없음)
마지막부분 : 디지털서명(생략가능) signature (서명은 통해 변경됐는지 확인 가능)
기존 mysql로 되어있는 것을 아래처럼 h2 메모리 db로 변경
#spring.datasource.url=jdbc:mysql://localhost/spring?useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.initialization-mode=always
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
h2 db 쓰기 위해서 config 추가
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
http.authorizeRequests()
.anyRequest().permitAll();
}
}
이렇게하고 schema.sql 지우면
자동으로 스키마 만들어짐
특별한건 없고 config에서 passwordencoder 추가
//패스워드인코더 -> 저장된 암호를 해싱하는 암호 인코더
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
jpa엔티티와 리파지토리
@Entity
public class Otp {
@Id
private String username;
private String code;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
@Entity
public class User {
@Id
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
public interface OtpRepository extends JpaRepository<Otp, String> {
Optional<Otp> findOtpByUsername(String username);
}
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findUserByUsername(String username);
}
컨트롤러 부분
@RestController
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/user/add")
public void addUser(@RequestBody User user) {
userService.addUser(user);
}
@PostMapping("/user/auth")
public void auth(@RequestBody User user) {
userService.auth(user);
}
@PostMapping("/otp/check")
public void check(@RequestBody Otp otp, HttpServletResponse response) {
if (userService.check(otp)) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
public void addUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
유저 추가하는 부분
정상호출일 경우
아래와 같이 user 추가됨.
아래와 같이 패스워드인코더를 통해서 암호화됨.
//유저 추가
public void addUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
user/auth체크하는 부분
if (passwordEncoder.matches(user.getPassword(), u.getPassword())) {
renewOtp(u);
} else {
throw new BadCredentialsException("Bad credentials.");
}
이런식으로 matches를 통해서 비밀번호 일치 비교
결과값
auth check를 할 때마다 otp 코드 새로됨.
select * from otp
USERNAME CODE
danielle 1420
두번째 예제에서는 첫번째 예제와 통합되는 화면인데
@Configuration
public class ProjectConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
resttemplate을 통해서 http 요청을 가능하게 해줌.
정리하자면
1번서버(인증서버) / 2번서버(비즈니스서버)
2번서버에서 아이디,패스워드 요청
1번서버가서 인증후에 코드값 전달
2번서버에서 아이디,패스워드,코드값 전달할 경우
코드값이 있으면 json토큰 발급후에 전달
발급된 토큰으로 /test/인증가능
이런식으로 두번째 프로젝트에 url 헤더에 아이디와 패스워드 입력
그럼 아래와 같이 /login 입력시 필터를 타게됨.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterAt(
initialAuthenticationFilter,
BasicAuthenticationFilter.class)
.addFilterAfter(
jwtAuthenticationFilter,
BasicAuthenticationFilter.class
);
http.authorizeRequests()
.anyRequest().authenticated();
}
그리고 중간에 프록시 서버를 둬서 1번과 2번 연결
@Component
public class AuthenticationServerProxy {
@Autowired
private RestTemplate rest;
@Value("${auth.server.base.url}")
private String baseUrl;
public void sendAuth(String username, String password) {
String url = baseUrl + "/user/auth";
var body = new User();
body.setUsername(username);
body.setPassword(password);
var request = new HttpEntity<>(body);
rest.postForEntity(url, request, Void.class);
}
public boolean sendOTP(String username, String code) {
String url = baseUrl + "/otp/check";
var body = new User();
body.setUsername(username);
body.setCode(code);
var request = new HttpEntity<>(body);
var response = rest.postForEntity(url, request, Void.class);
return response.getStatusCode().equals(HttpStatus.OK);
}
}
설정파일은 properties에 있음
server.port=9090
auth.server.base.url=http://127.0.0.1:8080
jwt.signing.key=ymLTU8rq83j4fmJZj60wh4OrMNuntIj4fmJ
그리고 마지막으로 jwt를 발급해주는 부분은
@Component
public class InitialAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private AuthenticationManager manager;
@Value("${jwt.signing.key}")
private String signingKey;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String username = request.getHeader("username");
String password = request.getHeader("password");
String code = request.getHeader("code");
if (code == null) {
Authentication a = new UsernamePasswordAuthentication(username, password);
manager.authenticate(a);
} else {
Authentication a = new OtpAuthentication(username, code);
manager.authenticate(a);
SecretKey key = Keys.hmacShaKeyFor(signingKey.getBytes(StandardCharsets.UTF_8));
String jwt = Jwts.builder()
.setClaims(Map.of("username", username))
.signWith(key)
.compact();
response.setHeader("Authorization", jwt);
}
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/login");
}
}
정상적으로 처리되면header에 authorization key값으로 발급됨.
json token 발급 확인.
'IT' 카테고리의 다른 글
파이썬 25542 (풀이중) (0) | 2023.09.04 |
---|---|
스프링시큐리티인액션 12장. oauth2 (0) | 2023.09.02 |
백준 파이썬 10845 큐 (0) | 2023.08.22 |
스프링시큐리티인액션 챕터10 (0) | 2023.08.17 |
스프링 시큐리티 인 액션 9장 (0) | 2023.08.12 |