Search
Duplicate
moon
sun

사용자 정의 인증

사용자 정의 인증

📕 Spring Security 5.7

이전 페이지

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

Code

Preview

1.
로그인 화면
2.
메인 화면

작업 프로세스

1.
프로젝트 생성
2.
프로젝트 설정
3.
도메인
4.
서비스
5.
스프링 시큐리티 설정

Preview

로그인 화면

메인 화면

프로젝트 생성

build.gradle

✅ spring boot 2.x.x
✅ spring security 5.x.x
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
복사