Search
Duplicate
moon
sun

바로 로그인

바로 로그인

📕 Spring Security 6

이전 페이지

💡
이전 페이지 내용에 이어서 진행합니다.

Code

Preview

1.
회원 가입 화면
2.
메인 화면

작업 프로세스

1.
프로젝트 생성
2.
프로젝트 설정
3.
서비스
4.
컨트롤러

Preview

1.
회원 가입 화면
2.
메인 화면

회원 가입 화면

✅
회원 가입 처리 ➡ 바로 로그인 ➡ 메인 화면
회원 가입 요청 시, 회원 가입이 성공하게 되면 바로 로그인 처리 후 메인 화면으로 이동합니다.

메인 화면

작업 프로세스

1.
프로젝트 생성
2.
프로젝트 설정
3.
스프링 시큐리티 설정
4.
서비스
5.
컨트롤러

프로젝트 생성

build.gradle

✅ spring boot 3.x.x
✅ spring security 6.x.x
plugins { id 'java' id 'war' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' } group = 'com.aloha' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
Java
복사

프로젝트 설정

application.properties

spring.application.name=form-jointologin # 데이터 소스 - MySQL spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aloha?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false&autoReconnection=true&autoReconnection=true spring.datasource.username=aloha spring.datasource.password=123456 # Mybatis 설정 mybatis.configuration.map-underscore-to-camel-case=true mybatis.type-aliases-package=com.aloha.security6.domain mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
복사

스프링 시큐리티 설정

SecurityConfig.java

package com.aloha.security6.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private DataSource dataSource; // 스프링 시큐리티 설정 메소드 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // ✅ 인가 설정 http.authorizeHttpRequests(auth -> auth .requestMatchers("/**").permitAll() .anyRequest().permitAll() ); // 🔐 폼 로그인 설정 http.formLogin( login -> login.permitAll() ); // 🔄 자동 로그인 설정 http.rememberMe(me -> me.key("aloha") .tokenRepository(tokenRepository()) .tokenValiditySeconds(60 * 60 * 24 * 7)); // 7일 유효시간 (초단위) return http.build(); } /** * 🍃 비밀번호 암호화 빈 등록 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 🍃 AuthenticationManager 인증 관리자 빈 등록 * @param authenticationConfiguration * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authenticationConfiguration ) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } /** * 🍃 JDBC 인증 방식 빈 등록 * @return */ @Bean public UserDetailsService userDetailsService() { JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(dataSource); // 사용자 인증 쿼리 String sql1 = " SELECT username, password, enabled " + " FROM user " + " WHERE username = ? " ; // 사용자 권한 쿼리 String sql2 = " SELECT username, auth " + " FROM user_auth " + " WHERE username = ? " ; userDetailsManager.setUsersByUsernameQuery(sql1); userDetailsManager.setAuthoritiesByUsernameQuery(sql2); return userDetailsManager; } /** * 🍃 자동 로그인 저장소 빈 등록 * ✅ 데이터 소스 * ⭐ persistent_logins 테이블 생성 create table persistent_logins ( username varchar(64) not null , series varchar(64) primary key , token varchar(64) not null , last_used timestamp not null ); * 🔄 자동 로그인 프로세스 * ✅ 로그인 시 * ➡ 👩‍💼(ID, 시리즈, 토큰) 저장 * ✅ 로그아웃 시, * ➡ 👩‍💼(ID, 시리즈, 토큰) 삭제 * @return */ @Bean public PersistentTokenRepository tokenRepository() { // JdbcTokenRepositoryImpl : 토큰 저장 데이터 베이스를 등록하는 객체 JdbcTokenRepositoryImpl repositoryImpl = new JdbcTokenRepositoryImpl(); // ✅ 토큰 저장소를 사용하는 데이터 소스 지정 // - 시큐리티가 자동 로그인 프로세스를 처리하기 위한 DB를 지정합니다. repositoryImpl.setDataSource(dataSource); // 서버 실행 시, 자동 로그인 테이블 자동 생성 // repositoryImpl.setCreateTableOnStartup(true); // persistent_logins 테이블 생성 try { repositoryImpl.getJdbcTemplate().execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL); } catch (BadSqlGrammarException e) { log.error("persistent_logins 테이블이 이미 존재합니다."); } catch (Exception e) { log.error("자동 로그인 테이블 생성 중 , 예외 발생"); } return repositoryImpl; } }
Java
복사

서비스

UserService.java
UserServiceImpl.java

UserService.java

✅
login() 메소드를 추가로 정의합니다.
public interface UserService { // 로그인 public boolean login(Users user, HttpServletRequest request) throws Exception; // 조회 public Users select(String username) throws Exception; // 회원 가입 public int join(Users user) throws Exception; // 회원 수정 public int update(Users user) throws Exception; // 회원 권한 등록 public int insertAuth(UserAuth userAuth) throws Exception; }
Java
복사

UserServiceImpl.java

✅
회원 가입 시, 입력한 로그인 아이디와 비밀번호를 통해 로그인 인증 처리가 되도록 구현합니다.
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManager; @Override public boolean login(Users user, HttpServletRequest request) throws Exception { // 💍 토큰 생성 String username = user.getUsername(); // 아이디 String password = user.getPassword(); // 암호화되지 않은 비밀번호 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); // 토큰을 이용하여 인증 Authentication authentication = authenticationManager.authenticate(token); // 인증 여부 확인 boolean result = authentication.isAuthenticated(); // 인증이 성공하면 SecurityContext에 설정 if (result) { SecurityContextHolder.getContext().setAuthentication(authentication); // 세션에 인증 정보 설정 (세션이 없으면 새로 생성) HttpSession session = request.getSession(true); // 세션이 없으면 새로 생성 session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); } return result; } @Override public Users select(String username) throws Exception { Users user = userMapper.select(username); return user; } @Override public int join(Users user) throws Exception { String username = user.getUsername(); String password = user.getPassword(); String encodedPassword = passwordEncoder.encode(password); // 🔒 비밀번호 암호화 user.setPassword(encodedPassword); // 회원 등록 int result = userMapper.join(user); if( result > 0 ) { // 회원 기본 권한 등록 UserAuth userAuth = new UserAuth(); userAuth.setUsername(username); userAuth.setAuth("ROLE_USER"); result = userMapper.insertAuth(userAuth); } return result; } @Override public int update(Users user) throws Exception { int result = userMapper.update(user); return result; } @Override public int insertAuth(UserAuth userAuth) throws Exception { int result = userMapper.insertAuth(userAuth); return result; } }
Java
복사

컨트롤러

- 📄 ~/controller/HomeController.java

📄 ~/controller/HomeController.java

✅
회원 가입 처리 성공 시, 로그인 요청 후 메인 화면으로 이동하도록 코드를 수정합니다.

이전 코드

int result = userService.join(user); if( result > 0 ) { return "redirect:/login"; }
Java
복사

수정 코드

// 암호화 전 비밀번호 String plainPassword = user.getPassword(); // 회원 가입 요청 int result = userService.join(user); // 회원 가입 성공 시, 바로 로그인 if( result > 0 ) { // 암호화 전 비밀번호 다시 세팅 // 회원가입 시, 비밀번호 암호화하기 때문에, user.setPassword(plainPassword); userService.login(user); return "redirect:/"; }
Java
복사

HomeController.java

@Slf4j @Controller public class HomeController { @Autowired private UserService userService; //** * 메인 화면 * 🔗 [GET] - / * 📄 index.html * @return * @throws Exception */ @GetMapping("") // public String home(Principal principal, Model model) throws Exception { // public String home(Authentication authentication, Model model) throws Exception { public String home(@AuthenticationPrincipal User authUser, Model model) throws Exception { log.info(":::::::::: 메인 화면 ::::::::::"); // if( authentication != null ) { // User authAuth = (User) authentication.getPrincipal(); // String username = authAuth.getUsername(); // log.info("username : " + username); // Users user = userService.select(username); // 사용자 정보 조회 // log.info("user : " + user); // model.addAttribute("user", user); // } // if (principal != null) { // String username = principal.getName(); // 인증된 사용자 아이디 // log.info("username : " + username); // Users user = userService.select(username); // 사용자 정보 조회 // log.info("user : " + user); // model.addAttribute("user", user); // 사용자 정보를 모델에 등록 // } else { // log.info("principal is null"); // } if( authUser != null ) { String username = authUser.getUsername(); // 인증된 사용자 아이디 log.info("username : " + username); Users user = userService.select(username); // 사용자 정보 조회 log.info("user : " + user); model.addAttribute("user", user); // 사용자 정보를 모델에 등록 log.info("authAuth : " + authUser); } return "index"; } /** * 회원 가입 화면 * 🔗 [GET] - /join * 📄 join.html * @return */ @GetMapping("/join") public String join() { log.info(":::::::::: 회원 가입 화면 ::::::::::"); return "join"; } /** * 회원 가입 처리 * 🔗 [POST] - /join * ➡ ⭕ 🔄🔐바로 로그인 ➡ / * ❌ /join?error * @param user * @return * @throws Exception */ @PostMapping("/join") public String joinPro(Users user) throws Exception { log.info(":::::::::: 회원 가입 처리 ::::::::::"); log.info("user : " + user); // 암호화 전 비밀번호 String plainPassword = user.getPassword(); // 회원 가입 요청 int result = userService.join(user); // 회원 가입 성공 시, 바로 로그인 if( result > 0 ) { // 암호화 전 비밀번호 다시 세팅 // 회원가입 시, 비밀번호 암호화하기 때문에, user.setPassword(plainPassword); userService.login(user); return "redirect:/"; } return "redirect/join?error"; } /** * 아이디 중복 검사 * @param username * @return * @throws Exception */ @ResponseBody @GetMapping("/check/{username}") public ResponseEntity<Boolean> userCheck(@PathVariable("username") String username) throws Exception { log.info("아이디 중복 확인 : " + username); Users user = userService.select(username); // 아이디 중복 if( user != null ) { log.info("중복된 아이디 입니다 - " + username); return new ResponseEntity<>(false, HttpStatus.OK); } // 사용 가능한 아이디입니다. log.info("사용 가능한 아이디 입니다." + username); return new ResponseEntity<>(true, HttpStatus.OK); } }
Java
복사

사용자 정보 가져오기

Principal vs Authentication vs User

💡
사용자 정보를 가져오는 주요 인터페이스 및 클래스
다음은 Spring Security의 주요 인터페이스와 클래스의 구조도입니다
java.security.Principal (Interface)Authentication (Interface)UsernamePasswordAuthenticationToken (구현체)getPrincipal()org.springframework.security.core.userdetails.User (implements UserDetails)
Java
복사
이 구조도는 Spring Security에서 사용자 인증 정보가 어떻게 계층화되어 있는지를 보여줍니다.
Principal은 최상위 인터페이스이며, Authentication은 이를 확장하여 더 많은 인증 관련 기능을 제공합니다. UsernamePasswordAuthenticationToken은 Authentication의 구현체 중 하나이며, getPrincipal() 메소드를 통해 UserDetails를 구현한 User 객체를 반환합니다.
클래스/인터페이스
역할
주요 메서드
주요 구현체
Principal
인증된 사용자의 기본 정보 제공 (표준 자바 인터페이스)
getName()
Spring Security의 User, 또는 사용자 정의 객체
Authentication
Spring Security 인증 정보 (사용자, 권한, 인증 상태 등 포함)
getPrincipal(), getAuthorities(), isAuthenticated()
UsernamePasswordAuthenticationToken, JwtAuthenticationToken
User
인증된 사용자의 세부 정보 제공 (Spring Security의 구현체, UserDetails를 구현)
getUsername(), getAuthorities(), isAccountNonLocked()
Spring Security 기본 User 클래스
Principal: 최소한의 사용자 식별 정보를 제공.
Authentication: 인증 및 권한 관련 정보를 모두 포함.
User: Spring Security에서 사용자 세부 정보를 제공하는 구체적인 구현체.