Spring Security Custom UserDetailsService(DB) 구현하기
이전에는 다음과 같이 USER를 메모리에 저장해놓고 Spring Security의 테스트를 하였다.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// add our Users for in memory authentication
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
하지만 실제로는 이렇게 사용하지 못하고 대부분 DB 등의 저장소에 사용자의 정보를 저장해놓고 사용해야 한다.
여기서는 DB에 저장해놓은 데이터를 JPA를 사용하여 가져오는 것을 구현하고자 한다.
application.properties
#JPA Configuration
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
mysql DB script
CREATE SCHEMA `quickguide` DEFAULT CHARACTER SET utf8mb4 ;
CREATE TABLE `user` (
`seq` int(11) NOT NULL,
`enabled` bit(1) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`seq`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `authority` (
`seq` int(11) NOT NULL,
`authority` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`seq`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- INSERT INTO `quickguide`.`users` (`seq`, `enabled`, `password`, `username`) VALUES ('1', 1, '1234', 'test');
INSERT INTO `quickguide`.`user` (`seq`, `enabled`, `password`, `username`) VALUES ('1', 1, '$2a$10$5rWSd1pL7FIbB1RVmM.2c.hXGowUjz0T/V1I.GlWGY7lVg4AKPxvu', 'test');
INSERT INTO `quickguide`.`authority` (`seq`, `authority`, `username`) VALUES ('1', 'USER', 'test');
JPA Entity
package com.quickguide.backend.authentication.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@Column(name = "seq")
@GeneratedValue(strategy = GenerationType.AUTO)
private int seq;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
}
package com.quickguide.backend.authentication.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "seq")
@GeneratedValue(strategy = GenerationType.AUTO)
private int seq;
@Column(name = "username")
private String username;
@Column(name = "authority")
private String authority;
}
JPA Repository
package com.quickguide.backend.authentication.repository;
import com.quickguide.backend.authentication.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UsersRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}
package com.quickguide.backend.authentication.repository;
import com.quickguide.backend.authentication.entity.Authority;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AuthoritiesRepository extends JpaRepository<Authority, Integer> {
List<Authority> findByUsername(String username);
}
Spring Security Config 클래스
package com.quickguide.backend.config;
import com.quickguide.backend.authentication.service.QuickGuideUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private QuickGuideUserDetailsService quickGuideUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/auth/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// add our Users for in memory authentication
// auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
auth.userDetailsService(quickGuideUserDetailsService);
}
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
1) 기존에 사용자 정보를 메모리에 저장하는 부분을 주석처리 하고 DB에서 사용자 정보를 가져오는 클래스로 변경한다.
auth.userDetailsService(quickGuideUserDetailsService);
2) 패스워드 인코딩 클래스 등록 (사용자가 입력한 패스워드가 DB에 저장되어 있는 패스워드와 동일한지 판단)
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
사용자 정의 UserDetails
package com.quickguide.backend.authentication.domain;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
public class QuickGuideUser implements UserDetails {
private String username;
private String password;
private boolean isEnabled;
private boolean isAccountNonExpired;
private boolean isAccountNonLocked;
private boolean isCredentialsNonExpired;
private Collection<? extends GrantedAuthority> authorities;
}
QuickGuideUserDetailsService - 사용자 정의 UserDetailsService
package com.quickguide.backend.authentication.service;
import com.quickguide.backend.authentication.domain.QuickGuideUser;
import com.quickguide.backend.authentication.entity.Authority;
import com.quickguide.backend.authentication.entity.User;
import com.quickguide.backend.authentication.repository.AuthoritiesRepository;
import com.quickguide.backend.authentication.repository.UsersRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Slf4j
@Service
public class QuickGuideUserDetailsService implements UserDetailsService {
@Autowired
private UsersRepository usersRepository;
@Autowired
private AuthoritiesRepository authoritiesRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = usersRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username + "is not found.");
}
QuickGuideUser quickGuideUser = new QuickGuideUser();
quickGuideUser.setUsername(user.getUsername());
quickGuideUser.setPassword(user.getPassword());
quickGuideUser.setAuthorities(getAuthorities(username));
quickGuideUser.setEnabled(true);
quickGuideUser.setAccountNonExpired(true);
quickGuideUser.setAccountNonLocked(true);
quickGuideUser.setCredentialsNonExpired(true);
return quickGuideUser;
}
public Collection<GrantedAuthority> getAuthorities(String username) {
List<Authority> authList = authoritiesRepository.findByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<>();
for (Authority authority : authList) {
authorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
}
return authorities;
}
}
1) @Slf4j 은 Lombok에서 제공하는 어노테이션으로 log 관련 된 부분이므로 삭제해도 무방하다.
2) 사용자 UserDetailsService 를 구현하기 위해서는 UserDetailsService 인터페이스를 상속받아야 한다.
3) 유저의 상태를 저장하는 부분은 true 로 설정해 놓았다. (해당 부분의 사용에 대한 부분은 나중에 정리)
- 실제 운영에서는 유용하게 사용할 수 있다. (접속 30일 이상 지난 사용자는 Lock 처리 등)
quickGuideUser.setEnabled(true);
quickGuideUser.setAccountNonExpired(true);
quickGuideUser.setAccountNonLocked(true);
quickGuideUser.setCredentialsNonExpired(true);
이제 http://localhost:8080/ 입력 후 test / 1234 를 입력하면 로그인이 된다.
'spring-project' 카테고리의 다른 글
Spring Security를 이용한 Basic Authentication (0) | 2019.03.01 |
---|---|
REST API 인증 방법 (0) | 2019.03.01 |
SpringBoot Logback.xml 추가 (로그 설정) (0) | 2019.02.24 |
IntelliJ에서 Lombok 적용하기. (0) | 2019.02.24 |
Spring Boot에 Spring Security 적용하기 (Spring Security Java Config) (0) | 2019.02.23 |