바로 로그인
이전 페이지
이전 페이지 내용에 이어서 진행합니다.
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.xplugins {
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
~/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에서 사용자 세부 정보를 제공하는 구체적인 구현체.








