1. 개요

[Spring Boot] security를 사용한 user 생성 및 권한부여 -1 에서는 메모리에 user 정보를 올리고 사용하는 위험한 행동을 했었다..

그래서 mariaDB를 설치하고 user를 생성해서 비밀번호를 암호화하고, 권한까지 부여해서 테스트를 진행해보기로 했다.

 

2. 구현

* /src/main/resource/template 하위에 모든 html 페이지를 두고 application.yml에 아래와 같이 추가 후 진행.

############################추가############################

spring:

  thymeleaf:

    prefix: classpath:/template

###########################################################

2-1) User class 작성 

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="USER")
public class User {
    @Id
    private String username;//PK
    private String passwd;
    private String role;
    //Getter Setter 생략
}

* 주의 * 

@Table(name="USER")

mariaDB에 Table 명을 대문자로 USER로 작성해서 위와 같이 입력하였는데, 그대로 실행하면 'user' Table을 찾을 수 없다면서 에러가 발생한다.

대문자로 인식하도록 application.yml에 아래와 같이 추가해주어야 한다.

############################추가############################

spring:

  jpa:

    hibernate:

      naming:

        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

###########################################################

 

2-2) UserService 작성 

public interface UserService {
    void saveUser(String id,String passwd);
}

2-3) UserServiceImpl 작성 

@Service
public class UserServiceImpl implements UserService{

@Autowired
private UserRepository userRepository;

@Bean
PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

@Override
public void saveUser(String username, String passwd) {
  User user = new User();
  user.setUsername(username);
  user.setPasswd(passwordEncoder().encode(passwd));
  user.setRole("USER");
  userRepository.save(user);
}

user.setRole("USER"); <- 생성되는 모든 신규 user에 대해서는 USER role만 부여하겠다는 말이다.

BCryptPasswordEncoder()를 사용해서 password를 암호화해서 저장한다.

2-4) UserRepository 작성 

public interface UserRepository extends CrudRepository<User, Long>{
}
CrudRepository 를 사용하면 간단하게 crud 구현이 가능하다. 만세.

2-5) Security 작성

@Component
@EnableWebSecurity
public class Security extends WebSecurityConfigurerAdapter {

@Autowired
private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests()
        .antMatchers("/admin").hasAuthority("ADMIN")
        .antMatchers("/**").permitAll()  // 넓은 범위의 URL을 아래에 배치한다.
        .anyRequest().authenticated()
        .and()
        .formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
        .and()
        .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
        .addLogoutHandler(new TaskImplementingLogoutHandler()).permitAll().logoutSuccessUrl("/");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
        .dataSource(dataSource)
        .rolePrefix("ROLE_")
        //앞에 ROLE_을 붙여야 home.html에서 정상적으로 hasRole()구문을 인식
        .usersByUsernameQuery("select username, replace(passwd, '$2y', '$2a'), true from USER where username = ?")
        .authoritiesByUsernameQuery("select username, role from USER where username = ?");
    }    

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

* https://www.browserling.com/tools/bcrypt -> passwd BCrypt로 변환 사이트이다. 테스트를 위해 admin계정과 manager 계정은 직접 변환해서 DB에 입력해 주었다.

* .usersByUsernameQuery("select username, replace(passwd, '$2y', '$2a'), true from USER where username = ?")

  위 부분은 BCrypt로 변환했을 경우 prefix가 $2y로 되는 경우가 있다고 하여 일치하도록 치환을 진행한 후에 query를 수행하는 부분이다.

2-6) Controller 작성 

@Controller
public class TestController {
  @Autowired
  UserService userService;

  @GetMapping("/")
  public String home(ModelAndView mav) {
    return "/home.html";  
  }

  @ResponseBody
  @GetMapping("/test")
  public String test() {
    return "OK";
  }

  @ResponseBody
  @GetMapping("/adminOnly")
  public String adminOnly() {
    return "Secret Page";
  }

  @GetMapping("/login")
  public String loginForm() {
    return "/login-form.html";
  }
  @GetMapping("/sginup")
  public String sginupForm() {
    return "/sginup-form.html";
  }

  @PostMapping("/sginup")
  public String sginupProcess(@Param("username") String username, @Param("passwd") String passwd) {
    return "/home.html";
  }
}

 

2-7) Custom page 작성 - login-form.html(http://yoonbumtae.com/?p=1184 참고), sginup-form.html

login-form.html

        <form name="f" th:action="@{/login}" method="post">   
            <fieldset>
                <legend>Please Login</legend>
                <div th:if="${param.error}" class="alert alert-error">   
                    Invalid username and password.
                </div>
                <div th:if="${param.logout}" class="alert alert-success"> 
                    You have been logged out.
                </div>
                <label for="username">Username</label>
                <input type="text" id="username" name="username"/>  
                <label for="password">Password</label>
                <input type="password" id="password" name="password"/>  
                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>
            </fieldset>
        </form>

sginup-form.html

    <form name="UserRegistrationForm" th:action="@{/sginup}" method="post">
        <input type="text" name="username" placeholder="아이디를 입력해주세요">
        <input type="password" name="passwd" placeholder="비밀번호">
        <button type="submit">가입하기</button>
    </form>

 

3. 테스트

3-1) 초기 화면

3-2) 회원 가입

-. DB에 실제 생성 된 user 확인. 새로 생성된 user_test는 password가 암호화가 되어 들어갔지만 직접 생성한 user 계정은 암호화가 되어있지 않다.

실제로 user 계정을 사용해서 접속하면 login이 되지 않는다.

3-3) Login

-. user_test 계정으로 접속