Dependencies 추가
프로젝트 생성 완료
설정
IntellJ IDEA -> Preferences
Build, Execution, Deployment 메뉴 -> Compiler -> Annotation Processors
에서 Enable annotatino processing 체크 후 저장
JDK 설치(by homebrew)
1. Homebrew 설치 및 업데이트
brew update
brew tap adoptopenjdk/openjdk
brew search jdk
brew install --cask adoptopenjdk11
/usr/libexec/java_home -V
java --version
brew install mysql
brew services start mysql
mysql -uroot
mysql_secure_installation
exit
brew services stop mysql
mysql -uroot -p
use mysql
CREATE DATABASE {데이터베이스이름};
사용자 생성
CREATE USER '{유저네임}'@'localhost' IDENTIFIED BY '{비밀번호}';
어떤 클라이언트에서든 접근 가능하게 설정
CREATE USER '{username}'@'%' IDENTIFIED BY '{password}';
권한부여
GRANT ALL PRIVILEGES ON {database}.* TO '{username}'@'localhost';
권한 부여한 것을 DBMS에 적용
FLUSH PRIVILEGES;
데이터베이스 접속
mysql -h127.0.0.1 -u{username} -p {database}
User Table
CREATE TABLE User(
`id` varchar(15),
`password` varchar(500),
`isAccountNonExpired` tinyint(1),
`isAccountNonLocked` tinyint(1),
`isCredentialsNonExpired` tinyint(1),
`isEnabled`tinyint(1)
) ENGINe=InnoDB DEFAULT CHARSET=utf8;
Authority Table
CREATE TABLE Authority(
`username` varchar(20),
`authority_name` varchar(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
<build.gradle>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
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.thymeleaf.extras:thymeleaf-extras-springsecurity5'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'mysql:mysql-connector-java:5.1.47'
// mybatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1'
}
mybatis란?
mybatis에서 spring-boot-starter와 연동하도록 지원해주는 모듈 있음
build.gradle에 의존성 추가
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2")
implementation('org.springframework.boot:spring-boot-starter-jdbc')
implementation('mysql:mysql-connector-java')
mysql에 접속하기 위한 설정
spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/DB이름?autoReconnect=true&useSSL=false
username: username(특별히 정해준적 없다면 root)
password: userpassword
Mapper 생성
Controller가 사용자가 유효한지 검증하고 그다음에 유저객체 만들어서 Repository를 통해 저장.
그 다음 사용자가 로그인하려고할때 영속화된 데이터를 읽어와서 순간 Spring Security가 유저정보를 읽어와서(UserDetail이라는 객체를 통해) 패스워드비교해서 맞으면 authority 주고 원하는 페이지로 이동시켜줌
우리가 쓰는 걸 userDetail로 바꿔줘야 됨
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
의존성 적용
View-Tool Windows - Project 누르고
해당 프로젝트 우클릭 - Reload Gradle Project
주의: User라는 이름으로 제공되는 클래스가 이미 있으니, 다른 이름을 사용할 것! 보통 Account를 쓴다고 함
Account.java
package com.example.project1.account;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Data;
public class Account implements UserDetails{
private String id;
private String password;
private boolean isAccountNonExpired;
private boolean isAccountNonLocked;
private boolean isCredentialsNonExpired;
private boolean isEnabled;
private Collection <? extends GrantedAuthority> authorities;
public String getId() {
return id;
}
public void setId(String id){
this.id=id;
}
@Override
public Collection <? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return this.authorities;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return this.password;
}
public void setPassword(String password){
this.password = password;
}
public void setAccountNonExpired(boolean isAccountNonExpired){
this.isAccountNonExpired = isAccountNonExpired;
}
public void setAccountNonLocked(boolean isAccountNonLocked){
this.isAccountNonLocked = isAccountNonLocked;
}
public void setCredentialsNonExpired(boolean isCredentialsNonExpired){
this.isCredentialsNonExpired = isCredentialsNonExpired;
}
public void setEnabled(boolean isEnabled){
this.isEnabled = isEnabled;
}
public void setAuthorities(Collection <? extends GrantedAuthority> authorities){
this.authorities = authorities;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return this.id;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return this.isAccountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return this.isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return this.isCredentialsNonExpired;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return this.isEnabled;
}
}
아까 DB에서 정의한 User Table이 이 Account Class에서 extend하고 있는 UserDetails interface를 기반으로 한 것!
AccountMapper.java
package com.example.project1.account;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface AccountMapper {
@Select("SELECT * FROM USER WHERE id=#{id}")
Account readAccount(String id);
@Select("SELECT authority_name FROM AUTHORITY WHERE username=#{id}")
List readAuthorities(String id);
@Insert("INSERT INTO USER VALUES(#{account.id},#{account.password},#{account.isAccountNonExpired},#{account.isAccountNonLocked},#{account.isCredentialsNonExpired},#{account.isEnabled})")
void insertUser(@Param("account") Account account);
@Insert("INSERT INTO AUTHORITY VALUES(#{id},#{authority})")
void insertUserAuthority(@Param("id") String id, @Param("authority") String authority);
@Select("SELECT* FROM USER")
List readAllUsers();
}
실제로 db에 값을 넣어 줄 매퍼
AccountService.java
package com.example.project1.account;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import com.example.project1.account.Account;
import org.hibernate.annotations.common.util.impl.LoggerFactory;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AccountService implements UserDetailsService {
@Autowired
AccountRepository accounts;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
Account account = accounts.findById(username);
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
account.setAuthorities(getAuthorities(username));
UserDetails userDetails = new UserDetails() {
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return account.getId();
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return account.getPassword();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return account.getAuthorities();
}
};
return account;
}
public Account save(Account account, String role) {
// TODO Auto-generated method stub
account.setPassword(passwordEncoder.encode(account.getPassword()));
account.setAccountNonExpired(true);
account.setAccountNonLocked(true);
account.setCredentialsNonExpired(true);
account.setEnabled(true);
return accounts.save(account, role);
}
public Collection<GrantedAuthority> getAuthorities(String username) {
List<String> string_authorities = accounts.findAuthoritiesById(username);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String authority : string_authorities) {
authorities.add(new SimpleGrantedAuthority(authority));
}
return authorities;
}
}
save는 회원가입 대신 유저를 추가해줄 임의의 메서드.
PasswordEncoder은 Spring Security가 우리 대신 패스워드를 암호화해주는 인터페이스
SecurityConfig.java
package com.example.project1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// configure 메서드는 어떤 URL path를 secure할지 말지 설정
http
.authorizeRequests()
.antMatchers("/", "/login","/home","/create").permitAll() // home은 아무나 허용
.anyRequest().authenticated() //
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
authorizeRequest()는 각 경로에 따른 권한 지정
/resources/**는 css, js 등 뷰 구현고 관련된 파일 권한
hasRole은 괄호 안의 권한을 가진 유저만 해당 경로에 접근할 수 있도록 설정
이때 자동으로 앞에 "ROLE"이 삽입되므로, Authority Table에 사용자의 권한을 삽입할 때 "ROLE권한명"형식으로 삽입해야 됨.
formLogin() 아래는 .loginPage(), .defaultSuccessPage() 등으로 직접 구현한 폼 로그인 성공 시 이동할 경로 지정 가능
AccountRepository.java
package com.example.project1.account;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class AccountRepository {
@Autowired
AccountMapper accountMapper;
public Account save(Account account, String role) {
accountMapper.insertUser(account);
accountMapper.insertUserAuthority(account.getId(), role);
return account;
}
public Account findById(String username) {
// TODO Auto-generated method stub
return accountMapper.readAccount(username);
}
public List<String> findAuthoritiesById(String username) {
return accountMapper.readAuthorities(username);
}
}
사용자 계정과 권한을 인자로 받아 DB에 삽입.
AccountController.java
사용자 추가할 경로 추가
package com.example.project1.account;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AccountController {
@Autowired
AccountService accountService;
@Autowired
AccountMapper accountMapper;
//ADMIN 계정 부여
@GetMapping("/create")
public Account create(){
Account account = new Account();
account.setId("admin");
account.setPassword("1234");
accountService.save(account, "ROLE_ADMIN");
return account;
}
//서비스 권한 부여
}
localhost:8080/create로 접속하면 사용자 추가 되고 암호화된 패스워드가 삽입되는 걸 확인할 수 있다!
추가된 사용자로 로그인하면 로그인 되는 걸 확인할 수 있다!!!
[참고문헌]
https://spring.io/guides/gs/securing-web/
https://a1010100z.tistory.com/14?category=791304
https://spring.io/guides/gs/securing-web/