[Spring Security] HTTP Basic, Form Login, Custom JDBC
5. HTTP Basic
HTTP Basic 인증
- RFC7617에서 정의된 기본 인증 방식
- 사용자 자격 증명을 Base64로 인코딩하여 서버에 전송한다.
- 간단하지만, 자격 증명이 네트워크를 통해 평문으로 전송되기 때문에 암호화되지 않은 상태에서는 보안에 취약하다. → HTTPS와 함께 사용하는 것이 일반적이다.
- HTTP 요청에서 Authorization 헤더에 사용자의 username과 password가 Base64로 인코딩되어 전송된다.
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
인증되지 않은 사용자가 리소스에 접근할 때
- 인증되지 않은 사용자가 인증이 필요한 리소스(ex.
/private)에 접근하면, 스프링 시큐리티의 AuthorizationFilter가 해당 요청을 감지하여 AccessDeniedException을 발생시킨다. - 이후 ExceptionTranslationFilter가 이를 처리하고, BasicAuthenticationEntryPoint가 동작하여 클라이언트에게 WWW-Authenticate header를 포함한 응답을 보낸다.
- 클라이언트는 이 응답을 받고 사용자 자격 증명(username, password)을 다시 입력하도록 요청 받는다.
인증 성공/실패 처리 흐름
- 사용자가 자격 증명을 입력하면, BasicAuthenticationFilter는 인증을 위해 UsernamePasswordAuthenticationToken 토큰을 생성한다.
- 이 토큰은 AuthenticationManager에게 전달되어 인증 처리를 시도한다.
- 인증 실패 시
- 사용자의 자격 증명이 일치하지 않으면, SecurityContextHolder는 초기화되고, AuthenticationEntryPoint가 WWW-Authenticate 헤더를 다시 클라이언트에게 보낸다.
- 인증 성공 시
- 성공한 사용자의 자격 증명 및 권한 정보가 담긴 Authentication 객체가 SecurityContextHolder에 저장된다.
- 이후 BasicAuthenticationFilter가 다음 필터로 요청을 전달한다.
- 인증 실패 시
HTTP Basic 인증 설정
- 스프링 시큐리티는 별도의 설정 없이도 기본적으로 HTTP Basic 인증을 활성화해두었다.
- 직접 구성하는 경우, 명시적으로 HTTP Basic 인증을 활성화해야 한다.
- SecurityConfig.java
@Bean public SecurityFilterChain filterChain(HttpSecurity http) { http // HTTP Basic 인증 활성화 .httpBasic(withDefaults()); return http.build(); }
HTTP Basic 인증 실습
Security 전용 설정 파일인 SecurityConfig.java 생성
- SecurityConfig
- @Configuration : 스프링에서 설정 클래스를 나타내는 애노테이션
- @EnableWebSecurity : 스프링 시큐리티의 기본 설정을 활성화하는 애노테이션
- 스프링 시큐리티 관련 스프링 Bean 객체들을 관리할 별도의 클래스
- SecurityFilterChain을 별도로 등록
- 스프링 시큐리티가 기본으로 동작시키는 옵션이 아닌 개발자만의 별도 옵션을 사용하기 위해 해당 Bean을 커스터마이징할 수 있도록 빈을 등록한다.
- SecurityFilterChain은 스프링 시큐리티에서 필터체인을 관리하는 빈이다.
http.httpBasic(Customizer.withDefaults()): HTTP Basic 인증 방식을 활성화하고, 기본적으로 사용되는 formLogin 방식을 비활성화한다.
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.httpBasic(Customizer.withDefaults()); // HTTP Basic 인증 방식 사용, formLogin 방식을 비활성화 return http.build(); } } - localhost:8080 실행 시
- 화이트라벨 에러 페이지 발생
- 인증 관련 설정이 되어 있지만, 아직 인증을 요구하는 경로에 접근했을 때 로그인 폼을 제공하지 않기 때문이다.
- 인가 추가 설정
http.authorizeRequests(): 인가 설정을 시작한다.anyRequest().authenticated(): 모든 경로의 요청에 대해 인증된 사용자만 접근할 수 있도록 허용한다.
http.authorizeRequests() .anyRequest() .authenticated(); // 모든 요청에 대해 인증된 사용자만 접근 허용 서버 재실행
- 로그인 성공 시 화이트라벨 페이지 발생
- 인증은 성공했으나 로그인 후 리다이렉션할 페이지가 정의되지 않았기 때문
인증 처리 동작 테스트
localhost:8080/hello에 접속 시, 인증되지 않은 상태에서는 HTTP Basic 인증 창이 팝업되며, 로그인을 요구한다.
- 로그인 후 동작
- Authorization 헤더에 사용자 정보가 포함되어 모든 요청에 대해 인증이 처리된다.
- 이후 다른 리소스에 접근할 때도 동일한 인증 정보가 헤더에 포함된 상태로 전송된다.
- 문제점
- Authorization 헤더에 Base64로 인코딩된 사용자 정보가 지속적으로 전송되기 때문에 안전하지 않다.
- 별도의 암호화 없이 인증 정보를 노출시킬 수 있다.
- 중간에 통신이 가로채기될 경우 보안 위험이 발생할 수 있다.
Postman 환경에서 인증 처리 동작 테스트
- localhost:8080/hello 로 요청 시
- 인증되지 않은 상태이기 때문에 401 Unauthorized 응답을 받는다.
- 해당 리소스가 인증된 사용자만 접근할 수 있도록 설정되어 있다.
- Postman에서 Basic Auth 설정
- Authorization 탭에서 Type을 Basic Auth 변경 후, user와 password 입력한다.
- 올바른 사용자 정보 입력 후 요청을 보내면 200 OK 응답을 받고,
/hello리소스에 접근이 허용된다.
Http 요청 헤더에 Authorization 헤더값 직접 추가
- Postman에서 Basic Auth 설정을 제거하고, 수동으로 인증을 처리하는 방법이다.
- Base64 인코딩
- https://www.base64encode.org/ 사이트를 이용해
user:password형식으로 Base64로 인코딩한다. - ex.
user:12736c97-b8f5-41de-a0bb-7988be3b830e→dXNlcjoxMjczNmM5Ny1iOGY1LTQxZGUtYTBiYi03OTg4YmUzYjgzMGU=
- https://www.base64encode.org/ 사이트를 이용해
- Authorization 헤더 추가
- Postman의 Headers 탭에서 Authorization 헤더를 수동으로 추가한다.
- 값은 Basic 뒤에 Base64로 인코딩한 값을 붙여서 입력한다.
- ex.
Basic dXNlcjoxMjczNmM5Ny1iOGY1LTQxZGUtYTBiYi03OTg4YmUzYjgzMGU= - 요청 전송 시, 헤더에 올바른 인증 정보가 포함되어 200 OK 응답을 받을 수 있다.
- HTTP 요청 헤더에 직접 Authorization 값을 추가하는 방법 → 인증 정보가 네트워크를 통해 인코딩된 상태로 전송되므로 보안이 취약할 수 있다.
- HTTPS와 같은 보안 프로토콜을 사용하여 데이터를 암호화하는 것이 필요하다.
6. Form Login 인증
Form 기반 인증
스프링 시큐리티는 HTML
<form>태그를 통해 사용자가 username과 password를 입력하여 인증을 처리하는 방법을 제공한다.<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"> <head> <title>Please Log In</title> </head> <body> <h1>로그인</h1> <form action="login" method="post"> <input type="text" name="username" placeholder="Username"/> <input type="password" name="password" placeholder="Password"/> <input type="submit" value="로그인" /> </form> </body> </html>
Form 로그인 인증 처리 흐름
- 인증되지 않은 사용자가 인증이 필요한 리소스에 접근
- 인증이 필요한 리소스(ex.
/private)에 접근하면 AuthorizationFilter가 요청을 감지하고, 인증되지 않는 경우 AccessDeniedException을 던진다.
- 인증이 필요한 리소스(ex.
- 로그인 페이지로 리다이렉트
- ExceptionTranslationFilter가 동작하여, LoginUrlAuthenticationEntryPoint가 사용자를 로그인 페이지(
/login)로 리다이렉트한다. - 이 때, HTTP 응답 헤더에
Location : /login값을 담아 브라우저가 로그인 페이지로 이동하도록 안내한다.
- ExceptionTranslationFilter가 동작하여, LoginUrlAuthenticationEntryPoint가 사용자를 로그인 페이지(
- 로그인 페이지 접근 및 인증 시도
- 사용자가 로그인 페이지에서 username과 password를 입력하여 인증 정보를 서버에 제출한다.
Spring Security 내부 처리 흐름
- 자격 증명 처리
- 사용자가 폼에 입력한 username과 password는 서버에 전달되며, 스프링 시큐리티의 UsernamePasswordAuthenticationFilter에서 처리한다.
- UsernamePasswordAuthenticationFilter는 HttpServletRequest 객체로부터 자격 증명 정보를 추출한 후, UsernamePasswordAuthenticationToken 토큰을 생성한다.
- AuthenticationManager에 토큰 전달
- 생성된 UsernamePasswordAuthenticationToken은 AuthenticationManager에게 전달되어 인증 처리가 진행된다.
- 인증 성공 시, 사용자는 로그인 상태로 전환된다.
- 실패 시, 에러메시지나 로그인한 페이지로 다시 리다이렉트된다.
Form Login 활성화
스프링 시큐리티 설정 클래스에 아래 코드를 추가해야 한다.
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .formLogin(withDefaults()); // Form Login 방식을 활성화 // 추가적인 설정들... return http.build(); }
Form 기반 Login 실습
SecurityConfig에서 HttpBasic 인증을 비활성화하고, form 기반 로그인 방식을 활성화한다.
// Spring Security 관련 스프링 빈들을 설정하는 설정 파일 @Configuration @EnableWebSecurity //(debug = true) public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // HttpBasic 인증 방식 비활성화 // http.httpBasic(Customizer.withDefaults()); // form login 방식 활성화 http.formLogin(Customizer.withDefaults()); http.authorizeRequests() .anyRequest() .authenticated(); return http.build(); } }- 디버깅 모드 활성화
@EnableWebSecurity**(debug = true)**설정을 추가하여 보안 설정의 디버깅을 활성화한다.
Using generated security password: b82a5ace-2282-4520-b4f5-6a6f49d25b2e This generated password is for development use only. Your security configuration must be updated before running your application in production. 2024-09-13 12:12:02.455 INFO 22536 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@f9f3928, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@404eca05, org.springframework.security.web.context.SecurityContextPersistenceFilter@1386313f, org.springframework.security.web.header.HeaderWriterFilter@4a37191a, org.springframework.security.web.csrf.CsrfFilter@67de7a99, org.springframework.security.web.authentication.logout.LogoutFilter@2cd62003, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@593a6726, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@433c6abb, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@288f173f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@58b91d57, org.springframework.security.web.session.SessionManagementFilter@12bbfc54, org.springframework.security.web.access.ExceptionTranslationFilter@1a0d96a5, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@6232ffdb] 2024-09-13 12:12:02.459 WARN 22536 --- [ main] o.s.s.c.a.web.builders.WebSecurity : ******************************************************************** ********** Security debugging is enabled. ************* ********** This may include sensitive information. ************* ********** Do not use in a production system! ************* ******************************************************************** 2024-09-13 12:12:02.679 INFO 22536 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2024-09-13 12:12:02.691 INFO 22536 --- [ main] dev.syntax.security.SecurityApplication : Started SecurityApplication in 1.445 seconds (JVM running for 2.092)- 디버깅 모드 활성화
- 실행시 로그
- debug log는 요청이 들어올 때 어떤 필터 체인이 적용되는 지 상세하게 보여준다.
2024-09-13 12:14:09.235 INFO 22536 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-09-13 12:14:09.235 INFO 22536 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-09-13 12:14:09.236 INFO 22536 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms 2024-09-13 12:14:09.244 INFO 22536 --- [nio-8080-exec-1] Spring Security Debugger : ************************************************************ Request received for GET '/hello': org.apache.catalina.connector.RequestFacade@be0d536 servletPath:/hello pathInfo:null headers: host: localhost:8080 connection: keep-alive cache-control: max-age=0 authorization: Basic dXNlcjpkMGQ3ZDRkOC1kMTY1LTRlZDQtOWQ4MC01YTIyOTJjNWI4OTU= sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" upgrade-insecure-requests: 1 user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 sec-fetch-site: none sec-fetch-mode: navigate sec-fetch-user: ?1 sec-fetch-dest: document accept-encoding: gzip, deflate, br, zstd accept-language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7 cookie: JSESSIONID=CB234FCA9D9C34D960022156415AACA0 Security filter chain: [ DisableEncodeUrlFilter WebAsyncManagerIntegrationFilter SecurityContextPersistenceFilter HeaderWriterFilter CsrfFilter LogoutFilter BasicAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor ] /...
UsernamePasswordAuthenticationFilter.class파일에서 아무 메서드에 디버그 브레이크 포인트를 찍으면 메서드가 실행됐는지, 값이 들어왔는 지를 확인할 수 있다.서버 실행 후 로그인 하면 디버깅이 시작된다.
UserDetailsService 커스터마이징
- 스프링 시큐리티에서 기본으로 제공하는 UserDetailsService를 커스터마이징하여 InMemoryUserDetailsManager로 사용자를 메모리 상에서 관리하도록 한다.
- InMemoryUserDetailsManager : 임시로 애플리케이션의 실행기간 동안만 유지시킬 수 있도록 메모리 상에서만 관리한다.
- 스프링에서 관리하는 UserDetails를 사용하여 테스트 용도로 사용할 User 객체를 생성한다.
userDetailsService.createUser(sampleUser): UserDetailsService에게 해당 테스트 User를 관리하도록 추가한다.
@Bean public UserDetailsService userDetailsService () { System.out.println("userDetailsService() called"); var userDetailsService = new InMemoryUserDetailsManager(); UserDetails sampleUser = User.withUsername("user1") .password("1234") .authorities("read") .build(); userDetailsService.createUser(sampleUser); return userDetailsService; } - 실행 시
- UserDetailsService가 호출된다.
2024-09-13 14:14:08.335 INFO 9444 --- [ main] dev.syntax.security.SecurityApplication : Starting SecurityApplication using Java 11.0.24 on 3-05 with PID 9444 (C:\woori_workspace\20.spring-security\step03_form_login\build\classes\java\main started by 3-05 in C:\woori_workspace\20.spring-security\step03_form_login) 2024-09-13 14:14:08.338 INFO 9444 --- [ main] dev.syntax.security.SecurityApplication : No active profile set, falling back to 1 default profile: "default" 2024-09-13 14:14:09.091 INFO 9444 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2024-09-13 14:14:09.098 INFO 9444 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-09-13 14:14:09.099 INFO 9444 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.75] 2024-09-13 14:14:09.171 INFO 9444 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-09-13 14:14:09.171 INFO 9444 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 783 ms userDetailsService called /... 2024-09-13 14:14:09.581 INFO 9444 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2024-09-13 14:14:09.589 INFO 9444 --- [ main] dev.syntax.security.SecurityApplication : Started SecurityApplication in 1.544 seconds (JVM running for 2.174)- 로그인시 에러가 뜬다.
- 스프링 시큐리티는 기본적으로 PasswordEncoder를 사용하여 비밀번호를 암호화하거나 해시 처리하도록 요구한다.
- 그러나 별도의 PasswordEncoder가 설정되지 않으면 다음과 같은 예외가 발생한다.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:289) ~[spring-security-crypto-5.7.8.jar:5.7.8]- 문제 해결을 위해 PasswordEncoder 빈을 추가해야 한다.
- PasswordEncoder 추가
- NoOpPasswordEncoder : 비밀번호를 암호화나 해시를 적용하지 않고 일반 텍스트(평문)으로 처리하는 인코더
- 이 인코더는 Deprecated 되었으며, 운영 환경에서는 보안상 이유로 사용하면 안된다. (개발 초기 단계에서만 사용된다.)
@Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }- 로그인 성공 후 실행화면
- 세션 키값 제거 시
- Application 탭 내 Cookies에서 JSESSIONID를 제거 후, /hello 경로로 재 요청 시, 세션 키 값이 제거되었기 때문에 요청 헤더에 키 값을 전송하지 못하게 된다.
- 서버는 인증되지 않은 사용자로 간주하여 다시 로그인을 진행하도록 로그인 페이지로 리다이렉트 된다.
- NoOpPasswordEncoder : 비밀번호를 암호화나 해시를 적용하지 않고 일반 텍스트(평문)으로 처리하는 인코더
Custom UserDetails 구현
- SimpleUser
- 사용자 정의 클래스
- 이 클래스는 스프링 시큐리티의 사용자 정보를 확인하기 위한 User 객체로 활용되어야 한다.
- UserDetails를 구현하여 스프링 시큐리티에서 인식할 수 있도록 한다.
public class SimpleUser **implements UserDetails** { private final String username; private final String password; public SimpleUser(String password, String username) { this.password = password; this.username = username; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } } - UserDetailsService에 커스텀 사용자 등록
- 개발자가 만든 SimpleUser 객체도 UserDetailsService에 등록하여 하나의 사용자로 관리하도록 한다.
@Bean public UserDetailsService userDetailsService() { // .. SimpleUser sampleUser2 = new SimpleUser("user2", "1234"); userDetailsService.createUser(sampleUser2); return userDetailsService; }
정리
User.withUsername("user1")- User는 스프링 시큐리티에서 기본으로 제공하는 타입
- 그 타입을 가지고 생성한 임시 User 계정
- org.springframework.security.core.userdetails.User 타입
new SimpleUser("user2", "1234");- 개발자가 직접 작성한 User 계정
- 대신 스프링 시큐리티에서 인식할 수 있게 하기 위해 UserDetails라는 타입을 구현한 하위 클래스가 되어야 함
custom jdbc 실습
build.gradle에
spring-boot-starter-data-jdbc와 MySQL 연결을 위한 의존성을 추가한다.implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' runtimeOnly 'com.mysql:mysql-connector-j'- application.properties에서 데이터베이스 연결을 설정한다.
- MySQL DB의 URL, 사용자명, 비밀번호를 지정한다.
spring.application.name=step04_custom_jdbc // DB 설정 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/securityauthentication?serverTimezone=UTC&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=1234 // JPA 설정 spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect spring.jpa.hibernate.ddl-auto=none - DB 생성 및 데이터 추가
- 사용자와 권한 테이블을 생성하고 초기 데이터를 삽입한다.
- 초기 데이터
CREATE TABLE IF NOT EXISTS 'securityauthentication'.'users' ( 'id' INT NOT NULL AUTO_INCREMENT, 'username' VARCHAR(45) NULL, 'password' VARCHAR(45) NULL, 'enabled' INT NOT NULL, PRIMARY KEY ('id') ); CREATE TABLE IF NOT EXISTS 'securityauthentication'.'authorities' ( 'id' INT NOT NULL AUTO_INCREMENT, 'username' VARCHAR(45) NULL, 'authority' VARCHAR(45) NULL, PRIMARY KEY ('id') ); INSERT INTO 'securityauthentication'.'authorities' VALUES (NULL, 'gugu', 'write'); INSERT INTO 'securityauthentication'.'users' VALUES (NULL, 'gugu', '1234', '1'); - user 데이터 확인
- 아래로 로그인 해보자.
JDBC 기반 사용자 조회 설정
- 스프링 시큐리티에서 사용자 정보를 DB에서 가져오기 위해 JdbcUserDetailsManager를 설정한다.
- JDBC 기반으로 DB에서 사용자 조회할 수 있도록 구현체를 지정한다.
- 오류 발생 → DataSource를 JdbcUserDetailsManager에 연결하여 JDBC 기반의 사용자 조회 기능을 활성화해야한다.
@Bean public UserDetailsService userDetailsService() { JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(); return jdbcUserDetailsManager; } - DataSource 추가
- 아래 설정으로 사용자 정보를 JDBC 기반으로 조회하여 로그인이 가능하게 된다.
@Bean public UserDetailsService userDetailsService(DataSource dataSource) { JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource); return jdbcUserDetailsManager; }[gugu, 1234]로 로그인 하면 실행된다!
커스텀 로그인 페이지 사용하기
- 요구사항
- NoOpPasswordEncoder가 아예 사라져서 못쓰게 되었음
- 현재 비밀번호는 평문으로 저장이 되어 있음
별도의 암호화 없이 그냥 평문으로 비밀번호를 검증할 수 있는 PlainTextPasswordEncoder라는 커스텀 클래스 생성하고, Bean으로 등록, 제대로 동작하는지 테스트
jsp나 타임리프처럼 서버에서 렌더링하는 방식으로 Get 요청 custom/login 경로로 요청 시, 내가 만든 login.html로 로그인 페이지가 동작할 수 있도록 구현
@Beanpublic PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.*getInstance*();}
- PlainTextPasswordEncoder 커스텀 클래스 생성
- 기존 PasswordEncoder 참조
- 개발자가 직접 구현한 암호 인코더로, 별도의 인코딩 없이 평문(Plain text)으로 DB에 저장
encode(CharSequence rawPassword): 사용자가 입력한 평문 비밀번호를 그대로 반환한다. (암호화 처리 X)
matches(CharSequence rawPassword, String encodedPassword)- rawPassword : 사용자가 입력한 비밀번호 값
- encodedPassword : DB에 저장된 인코딩된 비밀번호 값
- DB에 저장된 비밀번호와 사용자가 입력한 비밀번호를 비교한다.
public class PlainTextPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { return rawPassword.toString(); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { // 비밀번호 비교 로직 수행 부분 return rawPassword.toString().equals(encodedPassword); } }- SecurityConfig에서 passwordEncoder를 PlainTextPasswordEncoder로 변경한다.
- 직접 개발한 인코더를 시큐리티의 인증 메커니즘에 활용될 수 있도록 빈으로 등록
@Bean public PasswordEncoder passwordEncoder() { return new PlainTextPasswordEncoder(); }
- 직접 만든 login.html
- Thymeleaf를 사용해 폼을 생성하고,
/login으로 POST 요청을 보낸다.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"> <head> <title>Please Log In</title> </head> <body> <h1>Please Log In</h1> <div th:if="${param.error}"> Invalid username and password.</div> <div th:if="${param.logout}"> You have been logged out.</div> <form th:action="@{/login}" method="post"> <div> <input type="text" name="username" placeholder="Username"/> </div> <div> <input type="password" name="password" placeholder="Password"/> </div> <input type="submit" value="Log in" /> </form> </body> </html> - Thymeleaf를 사용해 폼을 생성하고,
- SecurityConfig의 filterChain 수정
- 스프링 시큐리티의 로그인 페이지 대신, 직접 만든 로그인 페이지를 사용하려면 SecurityFilterChain을 수정해야 한다.
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.formLogin(form -> form .loginPage("/login") .permitAll()); http.authorizeRequests() .anyRequest() .authenticated(); return http.build(); } //... @Bean public PasswordEncoder passwordEncoder() { return PlainTextPasswordEncoder.getInstance(); }
- LoginController 추가
- 커스텀 로그인 페이지를 보여주기 위한 LoginController를 작성한다.
login():/login요청이 들어오면 로그인 페이지(login.html)를 렌더링시킬 핸들러 메서드
@Controller public class LoginController { @GetMapping("/login") String login() { return "login"; } } build.gradle에 thymeleaf 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'- 실행 시
localhost:8080으로 접속시localhost:8080/login으로 이동한다.
- 로그인 성공 시
- 로그인 실패 시
우리 FISA 수업을 참고하였습니다.