> For the complete documentation index, see [llms.txt](https://dodeon.gitbook.io/study/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dodeon.gitbook.io/study/spring-security-in-action/06.md).

# 실전: 작고 안전한 웹 애플리케이션

## 6.1. 프로젝트 요구 사항과 설정

* 제품 정보와 사용자를 DB에 저장한다.
* 암호는 bcrypt나 scrypt로 해시된다.
* AuthenticationFilter는 요청을 가로채서 인증 책임을 AuthenticationManager에 위임한다.
* AuthenticationManager는 AuthenticationProvider로 요청을 인증한다.
* AuthenticationProvider는 인증 로직과 관련된 모든 사항을 구현한다.
  * AuthenticationProvider를 구현하는 AuthenticationProviderService를 구현한다.
  * UserDetailsService로 DB의 사용자 정보를 찾고 PasswordEncoder로 암호를 검증한다.
    * UserDetailsService는 사용자 관리를 담당한다.
  * JpaUserDetailsService가 데이터베이스 작업을 담당한다.
* 성공하고 반환된 세부 정보는 AuthenticationFilter에 의해 SecurityContext에 저장된다.

## 6.2. 사용자 관리 구현

```java

@Configuration
public class ProjectConfig {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SCryptPasswordEncoder sCryptPasswordEncoder() {
        return new SCryptPasswordEncoder(
                16384,   // CPU cost (N)
                8,     // Memory cost (r)
                1,     // Parallelization (p)
                32,    // Key length
                64     // Salt length
        );
    }
}
```

```java
public class CustomUserDetails implements UserDetails {

    private final User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getAuthorities().stream()
                .map(a -> new SimpleGrantedAuthority(a.getName()))
                .collect(Collectors.toList());
    }

    ...
}
```

```java

@Service
public class JpaUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public CustomUserDetails loadUserByUsername(String username) {
        Supplier<UsernameNotFoundException> s =
                () -> new UsernameNotFoundException("Problem during authentication!");

        User u = userRepository.findUserByUsername(username).orElseThrow(s);

        return new CustomUserDetails(u);
    }
}
```

* 인증에 사용할 값들을 구현한다.

## 6.3. 맞춤형 인증 논리 구현

```java

@Service
public class AuthenticationProviderService implements AuthenticationProvider {

    @Autowired
    private JpaUserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private SCryptPasswordEncoder sCryptPasswordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        CustomUserDetails user = userDetailsService.loadUserByUsername(username);

        return switch (user.getUser().getAlgorithm()) {
            case BCRYPT -> checkPassword(user, password, bCryptPasswordEncoder);
            case SCRYPT -> checkPassword(user, password, sCryptPasswordEncoder);
        };

    }

    @Override
    public boolean supports(Class<?> aClass) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
    }

    private Authentication checkPassword(CustomUserDetails user, String rawPassword, PasswordEncoder encoder) {
        if (encoder.matches(rawPassword, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
        } else {
            throw new BadCredentialsException("Bad credentials");
        }
    }
}
```

* 실제 인증 로직을 구현할 AuthenticationProvider를 구현한다.

```java

@Configuration
public class ProjectConfig {

    private final AuthenticationProvider authenticationProviderService;

    public ProjectConfig(AuthenticationProvider authenticationProviderService) {
        this.authenticationProviderService = authenticationProviderService;
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SCryptPasswordEncoder sCryptPasswordEncoder() {
        return new SCryptPasswordEncoder(
                16384,   // CPU cost (N)
                8,     // Memory cost (r)
                1,     // Parallelization (p)
                32,    // Key length
                64     // Salt length
        );
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(List.of(authenticationProviderService));
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.formLogin(form -> form.defaultSuccessUrl("/main", true))
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

        return http.build();
    }
}

```

* 구현한 프로바이더를 설정에 등록한다.
* formLogin으로 등록하고 기본 경로를 설정한다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dodeon.gitbook.io/study/spring-security-in-action/06.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
