IT

spring security in action chapter 11

프로개발러 2023. 8. 25. 17:46
반응형

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