웬만한 내용은 자세히 풀어 쓰는데
본 장에서는 그러지 못 할 것 같다.
정말 정말 많은 내용이 8장에 담겨 있기 때문이다.
스프링 시큐리티, SecurityFilterChain, UserDetails만 설명해도
읽는 사람이 질릴 정도로 많은 내용이 나올 거다.
그래서 나는 본 페이지를 참고하면
'스프링 시큐리티 최신 버전으로 8장 내용을 온전히 구현할 수 있게 되는 것'만을 목표로 한다.
여기까지는 아무 문제 없이 잘 했을 것이다.
그러나 다음 단계부터 문제가 발생하니...
책에 나와 있는 코드를 따라 치면 deprecated 어쩌구 등이 폭탄처럼 쏟아진다.
스프링부트 팀(?)에서 '응~ 이 코드에서 취약점 발견돼서 deprecated할 거야~ 마이그레이션 해~'라는 뜻이다.
자... 그럼 이 시점부터는 '생각'을 해야 한다.
ㅠ_ㅠ 저... 스프링'부트' 뉴빈데요... 이런 거 너무 가혹...
우선은 '에러 메시지', '로그', '코드' 등으로 검색한다.
하나 하나 읽으면서 지금 내가 구현하고자 하는 코드와 가장 적합한 코드를 찾아 본다.
공식 문서를 참고하며 문제를 해결해 나가면 가장 좋겠지만
'어디에' '무엇이 있는지'에 대한 정보가 아예 없는 사람이라면
그 방법은 힘들 것이다.
여튼 그렇게 나는 대체 코드를 찾았다.
이렇게 찾은 코드는 그냥 무지성으로 복붙할 게 아니다.
'생각'을 해 보자.
'이거 진짜 내가 구현하려는 기능의 코드가 맞나?'
//책에 나와 있는 코드
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(toH2Console())
.requestMatchers("/static/**");
}
//내가 바꾼 코드
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers(new AntPathRequestMatcher("/h2-console/**"))
.requestMatchers(new AntPathRequestMatcher("/static/**"));
}
원본 코드는 웹에서 특정 url로 들어갈 때 스프링 시큐리티 사용을 비활성화하겠다는 의미다.
원본 코드에서는 requestMatchers도 toH2Console도 사용이 안 됐다.
PathRequest 뭐 이런 거 붙여도 마찬가지였다.
근데 중요한 건 해당 url에 스프링 시큐리티 적용 안 하는 거잖아...
그러니까 주어진 패턴에 대한 요청을 일치시키는 AntPathRequestMatcher를 썼다.
new를 붙인 이유는, 패턴 정의 시 각각의 패턴에 대한 AntPathRequestMatcher 객체를 별도로 생성해야 하기 때문.
//책에 나와 있는 코드
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/articles")
.and()
.logout()
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.and()
.build();
}
//내가 바꾼 코드
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector)
throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(new MvcRequestMatcher(introspector, "/login")).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, "/signup")).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, "/user")).permitAll()
.anyRequest().authenticated())
.formLogin((formLogin) -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/articles"));
http
.logout((logout) -> logout.logoutSuccessUrl("/login")
.invalidateHttpSession(true));
http.csrf((csrf) -> csrf.disable());
return http.build();
}
원본 코드는
1. /login, /signup, /user 경로로는 아무나 들어와도 된다.
2. 다른 경로는 인가는 몰라도 인증은 되어 있어야 열린다.
3. formLogin할 건데 경로는 이렇고, 성공하면 여기로 간다.
4. 로그아웃 성공하면 여기로 간다, 세션도 전체 삭제한다.
5. CSRF 공격 대비 설정은 끈다(깃헙에서 복사한 위 코드에는 이 부분이 없지만 책에는 있다).
를 목표로 한다.
자... 그러면 수정한 곳에서도 이런 기능들이 되게 하면 되는 거 아냐?
원본 코드의 기능을 그대로 구현한 게 내가 수정한 코드다.
그럼
'HandlerMappingIntrospector 얘는 뭔가요?',
'왜 (new MvcRequestMatcher(introspector, "/login")) 이렇게 설정했나요',
라고 물을 수 있는데
(ㅠ_ㅠ)
전 모르면 그냥 모르겠다고 합니다...
Mvc~ 저거 Ant~로 대체도 가능한데 무슨 차이가 있는지 모르겠어서 Mvc~로 때려 박음.
이렇게 개발하면 절대 안 되는데
특히 스프링에서 제대로 알지도 못 하는 거 쓰는 거 금물인데
지금 시간이 없다...
//책에 나와 있는 코드
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
//내가 바꾼 코드
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return daoAuthenticationProvider;
}
얘는 원본 코드에서 .and()이 문제가 되었다.
이건 도대체 어떻게 바꾸나 머리를 쥐어 뜯고 있었는데
누군가 깃헙 이슈에서 아래 코드를 제시했고
내용을 보아하니 대체가 될 것 같아서 적용했다.
이 코드 관련해서는 바꾸는 법이 공식 문서에 여럿 나와 있었는데
나는 이게 LDAP 기반인지 JDBC 기반인지 뭐 그런 걸 알 수가 없었다.
그래서 그냥 범용적으로 사용이 될 것 같은 저 Dao~를 활용해서
userService 객체로 사용자 인증하고
bCrypt~로 비밀번호 암호화해서 넘기고
뭐 그렇게 해결했다...
여기까지 하고 localhost:8080/articles에 접속하면 로그인 페이지가 뜰 것이다.
근데 그 페이지에
1. 회원 가입 버튼이 없을 거고
2. section이 중앙 정렬이 안 되어 있을 거다.
<button type="button" class="btn btn-primary" onclick="location.href='/signup'">회원가입</button>
버튼은 달면 되고
/* section의 중앙 정렬을 위해서 추가 */
.d-flex.vh-100 {
display: flex;
justify-content: center;
align-items: center;
}
css는 추가하면 그만이다.
여기까지 했으면 진짜 끝이다.
로그인, 회원가입 화면이 예쁘게 나올 거고
기능도 다 잘 동작할 거다!
8장 코드만 편하게 보고 싶으면
https://github.com/JaeeunSkywalker/jaeeunTheBlackrabbit/tree/1ea4f9fd0fef1228058118d9a49958481926ba7b
여기를 이용하시면 된다.
이 글은 골든래빗 《스프링 부트 3 백엔드 개발자 되기 - 자바 편》의 8장 써머리입니다.
안녕하세요! 좋은 글 감사드립니다.
혹시 서버 실행 -> 회원가입 -> 로그인 후 submit 버튼을 눌렀을 때 articles 페이지로 잘 이동이 되셨나요? 저는 에러페이지와 함께 url은 http://localhost:8080/github-markdown-css?continue와 같이 뜨더라구요... spring security의 css 권한 문제인 거 같은데 동일한 문제가 없으셨을까요?