Table of Contents
Spring Security with JDBC(UserDetailsService)
전체소스
전체소스는 여기 에 공개되어 있습니다.
프로젝트 생성
웹서비스에 보안 적용 을 기초로 시작합니다.
의존성 추가
build.gradle 에 jdbc, jpa, mysql-connector 를 추가해 줍니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.security:spring-security-test'
}
application.properties 설정
# spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/db_example
spring.datasource.username=springuser
spring.datasource.password=ThePassword
spring.datasource.driver-class-name =com.mysql.jdbc.Driver
# spring.jpa.show-sql: true
User 추가
User.java
@Getter
@Setter
@Entity
@Table(name = "tbl_user", catalog = "db_example")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
@JoinColumn(name="uid")
private List<UserRole> roles;
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
UserRole.java
@Getter
@Setter
@Entity
@Table(name = "tbl_userrole", catalog = "db_example")
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private Long uid;
@Column(nullable = false)
private String rolename;
}
UserRoleRepository.java
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
}
UserDetailsService 추가
MyUserDetailsService.java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new MyUserDetails(user);
}
}
MyUserDetails.java
public class MyUserDetails implements UserDetails {
private final User user;
public MyUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
for (UserRole role : this.user.getRoles()) {
roles.add(new SimpleGrantedAuthority(role.getRolename()));
}
return roles;
}
@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 true;
}
}
CREATE TABLE `tbl_user` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL
)
COLLATE='utf8_general_ci'
ENGINE=INNODB;
INSERT INTO tbl_user(username, PASSWORD)
VALUES('111', '{noop}222');
CREATE TABLE `tbl_userrole` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`uid` INT(11) NOT NULL,
`rolename` VARCHAR(255) NOT NULL
)
COLLATE='utf8_general_ci'
ENGINE=INNODB;
INSERT INTO tbl_userrole(uid, rolename)
VALUES(1, 'ROLE_ADMIN');
INSERT INTO tbl_userrole(uid, rolename)
VALUES(1, 'ROLE_USER');
WebSecurityConfig 수정
기존에 userDetailsService()
를 제거합니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
// @Bean
// @Override
// public UserDetailsService userDetailsService() {
// UserDetails user =
// User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
//
// return new InMemoryUserDetailsManager(user);
// }
}
프로젝트 실행
프로젝트를 실행하면, 데이타베이스에 저장한 아이디/비밀번호를 이용해 로그인할 수 있습니다.
비밀번호 암호화
현재는 비밀번호가 데이타베이스 상에 평문으로 저장됩니다.
보안상 취약하므로 암호화된 비밀번호가 데이타베이스에 저장되도록 방식을 변경합니다.
암호화 비밀번호 생성
public class MakeBCryptPassword {
public static void main(String[] args) {
String password = "222";
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String hashedPassword = passwordEncoder.encode(password);
System.out.println(hashedPassword);
System.out.println(passwordEncoder.matches("222", hashedPassword));
}
}
위 파일을 실행하고, 로그에 표시되는 암호화 비밀번호를 복사해 데이타베이스에 저장합니다.
UPDATE tbl_user
SET PASSWORD = '$2a$10$7yXyY8.uGeP6PWs0P.yw9Ojvf94IfG9eyx75AKvTK9IDo0f3pZle6'
WHERE id = 1;
MyUserDetailsService 수정
MyUserDetailsService.java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new MyUserDetails(user);
}
public PasswordEncoder passwordEncoder() {
return this.passwordEncoder;
}
}
WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(userService.passwordEncoder())
;
}
}
프로젝트 재실행
프로젝트를 재실행하면 암호화된 비밀번호화 입력된 비밀번호를 비교하게 됩니다.
Pingback: Spring Boot 시작하기 – 상구리의 기술 블로그