토큰 - 특정 엔드포인트에 접근하려는 클라이언트가 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 |