사용자 정의 인증
이전 페이지
이전 페이지 내용에 이어서 진행합니다.
Code
Preview
1.
로그인 화면
2.
메인 화면
작업 프로세스
1.
프로젝트 생성
2.
프로젝트 설정
3.
도메인
4.
서비스
5.
스프링 시큐리티 설정
Preview
로그인 화면
메인 화면
프로젝트 생성
build.gradle
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '2.7.17'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.aloha'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '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:2.3.1'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
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:2.3.1'
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-custom
# 데이터 소스 - 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.form_custom.domain
mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
복사
도메인
•
Users.java
•
UserAuth.java
•
CustomUser.java
Users.java
@Data
public class Users {
private String id;
private String username;
private String password;
private String name;
private String email;
private String profile;
private Date createdAt;
private Date updatedAt;
private int enabled;
private List<UserAuth> authList;
}
Java
복사
UserAuth.java
@Data
public class UserAuth {
private String id;
private String username;
private String auth;
}
Java
복사
CustomUser.java
DB에서 조회한 데이터를 직접 정의한 사용자 및 권한 Users, UserAuth 객체로 매핑한 결과를 스프링 시큐리티의 사용자 정보 인터페이스인 UserDetails 를 구현하여 사용자 정의 인증 객체로 정의합니다.
@Getter
@ToString
public class CustomUser implements UserDetails {
// 사용자 DTO
private Users user;
public CustomUser(Users user) {
this.user = user;
}
/**
* 🔐 권한 정보 메소드
* ✅ UserDetails 를 CustomUser 로 구현하여,
* Spring Security 의 User 대신 사용자 정의 인증 객체(CustomUser)로 적용
* ⚠ CustomUser 적용 시, 권한을 사용할 때는 'ROLE_' 붙여서 사용해야한다.
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthList().stream()
.map( (auth) -> new SimpleGrantedAuthority(auth.getAuth()) )
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.getEnabled() == 0 ? false : true;
}
}
Java
복사
서비스
•
UserDetailServiceImpl.java
UserDetailServiceImpl.java
UserDetailsService 인터페이스를 구현하여, 사용자 정보를 로드하는 방법을 정의할 수 있습니다.
/**
* 🔐 UserDetailsService : 사용자 정보 불러오는 인터페이스
* ✅ 이 인터페이스를 구현하여, 사용자 정보를 로드하는 방법을 정의할 수 있습니다.
*/
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info(":::::::::: UserDetailServiceImpl ::::::::::");
log.info("- 사용자 정의 인증을 위해, 사용자 정보 조회");
log.info("- username : " + username);
Users user = null;
try {
// 👩💼 사용자 정보 및 권한 조회
user = userMapper.login(username);
} catch (Exception e) {
e.printStackTrace();
}
if( user == null ) {
throw new UsernameNotFoundException("사용자를 찾을 수 없습니다." + username);
}
// 🔐 CustomUser ➡ UserDetails
CustomUser customUser = new CustomUser(user);
return customUser;
}
}
Java
복사
스프링 시큐리티 설정
•
SecurityConfig.java
SecurityConfig.java
UserDetailsService 빈을 메소드로 정의하는 방식에서, @Service 클래스를 정의한 사용자 정의 인증 설정을 하는 방식으로 전환한다.
이전 방식 (AS-IS)
적용 방식 (TO-BE)
•
UserDetailServiceImpl 의존성 주입
•
사용자 정의 인증 설정
UserDetailServiceImpl 의존성 주입
@Autowired
private UserDetailServiceImpl userDetailServiceImpl;
Java
복사
사용자 정의 인증 설정
// ✅ 사용자 정의 인증 설정
http.userDetailsService(userDetailServiceImpl);
Java
복사
SecurityConfig.java
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailServiceImpl userDetailServiceImpl;
// 스프링 시큐리티 설정 메소드
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// ✅ 인가 설정
http.authorizeRequests(requests -> requests
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
);
// 🔐 폼 로그인 설정
// ✅ 커스텀 로그인 페이지
http.formLogin(login -> login.loginPage("/login")
.loginProcessingUrl("/login"));
// ✅ 사용자 정의 인증 설정
http.userDetailsService(userDetailServiceImpl);
// 🔄 자동 로그인 설정
http.rememberMe(me -> me.key("aloha")
.tokenRepository(tokenRepository())
.tokenValiditySeconds(60 * 60 * 24 * 7));
return http.build();
}
/**
* 👮♂️🔐 사용자 인증 관리 빈 등록 메소드
* JDBC 인증 방식
* ✅ 데이터 소스 (URL, ID, PW) - application.properties
* ✅ SQL 쿼리 등록
* ⭐ 사용자 인증 쿼리
* ⭐ 사용자 권한 쿼리
* @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);
// 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
복사







