티스토리 뷰
Spring Advanced : Web MVC + Security + Hibernate with Java Config - 유저 인증 및 등록 구현하기 6
Korean Eagle 2020. 5. 20. 02:591. 이 포스트는 Spring : Web MVC + Security + JDBC 시리즈에 연장이다. xml파일 설정은 사용하지 않는다.
1-1 하려는 것은 우선 in-memory로 인증을 구현한다.
1-2 Database를 생성하고 hibernate로 유저 등록을 구현한다.
1-3 가입정보에 대한 Validation처리를 작성한다. Customer Validatior로 구현한다.
1-4 In-memory가 아닌 DaoAuthenticationProvider로 hibernate를 사용한 Spring security 인증처리로 변경
1-5 위에 것을 한번에 다 할려면 난이도가 헬이라서 이렇게 분리해서 한다.
2. 이 포스트는 하이버네이트를 이용한 custom schema를 사용하여 Spring security 로그인에 연결하는 내용이다.
2-1 현재 상황은 로그인은 in-memory로 되어 있고
2-2 가입 유저들은 가입정보를 데이터베이스에 저장하고 있다.
2-3 이 포스트는 가입한 데이터베이스 정보를 가지고 실제 로그인에 사용하도록 하는 내용이다.
3. 우선 최종 인증설정변경에 앞서 중복 username 체크하는 부분에 메시지를 표출하는 것부터 하겠다.
3-1 현재는 그냥 중복 유저이름일 경우 그냥 registration-form을 돌아가도록 되어 있다.
3-2 여기서는 이미 username이 사용된 경우에 model에 중복이름이라는 내용을 넣어 등록페이지로 다시 보낸다.
3-3 마지막 포스트라 그냥 Login Controller 다 붙였다. 지금 이야기하는 것은 제일 아래 registerUser 메소드이다.
package pe.pilseong.custom_registration.controller;
import javax.validation.Valid;
...
@Controller
public class LoginController {
@Autowired
private UserService userService;
@GetMapping("/showLoginPage")
public String showLoginPage() {
return "plain-login";
}
@GetMapping("/access-denied")
public String accessDenied() {
return "access-denied";
}
@GetMapping("/registrationPage")
public String registrationPage(Model model) {
model.addAttribute("user", new UserDTO());
return "registration-form";
}
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
@PostMapping("/registerUser")
public String registerUser(Model model, @Valid @ModelAttribute("user") UserDTO userDTO, BindingResult result) {
System.out.println(userDTO.toString());
if (result.hasErrors()) {
return "registration-form";
}
if (this.userService.findByUserName(userDTO.getUserName()) != null) {
model.addAttribute("registrationError", "User name is already taken");
return "registration-form";
}
this.userService.save(userDTO);
return "redirect:/showLoginPage";
}
}
3-4 이제 jsp에서 표출하는 부분을 넣는다.
3-4-1 위치는 form:form 바로 뒤에 공백을 사이에 둔 로직이 있다. registrationError라는 게 있으면 내용을 보여준다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous">
<title>Registration Form</title>
</head>
<body>
<div class="container">
<div class="card" style="width: 350px; margin-left: auto; margin-right: auto; border: none;">
<h1 class="display-4">Registration</h1>
<form:form action="${pageContext.request.contextPath}/registerUser" method="POST" modelAttribute="user">
<div class="form-group">
<c:if test="${ registrationError != null }">
<div class='alert alert-danger'>
${ registrationError }
</div>
</c:if>
</div>
<div class="form-group">
<label for="username">Username</label>
<form:input type="text" id="username" name="userName" class="form-control" path="userName"/>
<form:errors path="userName" cssClass="text-danger"></form:errors>
</div>
<div class="form-group">
<label for="password">Password</label>
<form:input type="password" id="password" name="password" class="form-control" path="password" />
<form:errors path="password" cssClass="text-danger"></form:errors>
</div>
<div class="form-group">
<label for="matchingPassword">Confirm Password</label>
<form:input type="password" id="matchingPassword" name="matchingPassword" class="form-control" path="matchingPassword"/>
<form:errors path="matchingPassword" cssClass="text-danger"></form:errors>
</div>
<div class="form-group">
<label for="firstName">First Name</label>
<form:input type="text" id="firstName" name="firstName" class="form-control" path="firstName"/>
<form:errors path="firstName" cssClass="text-danger"></form:errors>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<form:input type="text" id="lastName" name="lastName" class="form-control" path="lastName"/>
<form:errors path="lastName" cssClass="text-danger"></form:errors>
</div>
<div class="form-group">
<label for="email">Email</label>
<form:input type="email" id="email" name="email" class="form-control" path="email" />
<form:errors path="email" cssClass="text-danger"></form:errors>
</div>
<input type="submit" value="Register" class="btn btn-primary">
</form:form>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
</body>
</html>
3-4-2 결과 화면이다.
4. 이제 본론인 인증을 변경하는 부분이다.
4-1 스프링 Security에서 customized된 데이터베이스 스키마를 사용하려면 AuthenticationProvider를 설정해야 한다.
4-1-1 이것은 스프링 Security가 로그인을 위해서 데이터를 어떻게 받아오는지 알려주어야 하는 부분이다.
4-2 스프링 Security는 편의성을 높이기 위해 DaoAuthenticationProvider라는 것을 제공하고 있다.
4-2-1 이 AuthenticationProvider는 UserDetailsService라는 인터페이스를 통하여 유저정보를 받아온다.
4-2-2 따라서 UserDetailsService 인터페이스를 구현하여 어떻게 유저정보를 얻어오는지 알려주어야 한다.
4-2-2-1 UserDetailsService 인터페이스는 loadUserByUsername(String username)이라는 하나의 메소드만 가지고,
4-2-2-2 org.springframework.security.core.userdetails.User를 반환한다.
4-2-2-3 이 메소드를 UserService에서 상속하여 구현하고 스프링 Security에서 필요한 User를 만들어야 한다.
4-2-2-4 이 구현클래스를 UserDetailsService인터페이스로 DaoAuthenticationProvider에 제공하면 된다.
4-3 UserDetailsService인터페이스를 상속한 UserService 인터페이스이다.
package pe.pilseong.custom_registration.service;
import org.springframework.security.core.userdetails.UserDetailsService;
import pe.pilseong.custom_registration.entity.User;
import pe.pilseong.custom_registration.user.UserDTO;
public interface UserService extends UserDetailsService {
User findByUserName(String userName);
void save(UserDTO user);
}
4-4 이제 이 메소드를 UserServiceImpl에서 추가로 구현해야 한다.
4-4-1 아래 부분에 Override된 loadByUsername이 있다.
4-4-2 로직은 username으로 DB에서 User객체를 가져와 스프링이 원하는 원하는 다른 User객체를 반환하는 것이다.
4-4-3 이름과 비밀번호를 설정하는 것은 문자열이라 단순한데
4-4-4 Role의 경우는 GrantedAuthority타입으로 된 객체의 List로 반환해서 입력해야 한다.
4-4-5 GrantedAuthority는 인터페이스인데 그냥 Role이라고 보면 된다.
4-4-5-1 이걸 구현한 SimpleGrantedAuthority는 속성으로 String role하나 만 꼴랑가지고 있다.
4-4-6 mapRolesToAuthorities는 그냥 List<Role>을 받아서 List<GrantedAuthority>로 바꾼 거라고 생각하면 된다.
4-4-7 Role Entity를 GrantedAuthority를 상속받게 하면 더 간단히 처리할 수 있다. 다른 시리즈에서 다룬다.
package pe.pilseong.custom_registration.service;
...
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Autowired
private RoleDAO roleDAO;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
@Transactional
public User findByUserName(String userName) {
return this.userDAO.findByUserName(userName);
}
@Override
@Transactional
public void save(UserDTO userDTO) {
User user = new User();
user.setUsername(userDTO.getUserName());
user.setPassword(this.passwordEncoder.encode(userDTO.getPassword()));
user.setFirstName(userDTO.getFirstName());
user.setLastName(userDTO.getLastName());
user.setEmail(userDTO.getEmail());
user.setRoles(Arrays.asList(this.roleDAO.findRoleByName("ROLE_EMPLOYEE")));
this.userDAO.save(user);
}
@Override
@Transactional
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = this.userDAO.findByUserName(userName);
if (user == null) {
throw new UsernameNotFoundException("Invalid username and password");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), this.mapRolesToAuthorities(user.getRoles()));
}
private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
4-4-7 stream을 사용하여 변경하는데 그냥 메소드 안만들고 한줄로 해도 된다.
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = this.userDAO.findByUserName(userName);
if (user == null) {
throw new UsernameNotFoundException("Invalid username and password");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(),
user.getRoles().stream().map(role-> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList()));
}
4-5 이제 마지막으로 SecurityConfig에서 인증을 DaoAuthenticationProvider로 설정하여 연결한다.
4-5-1 DaoAuthenticationProvider를 생성하는 @Bean이다.
4-5-2 데이터베이스에서 로그인 정보를 가져오는 UserDetailsService와 비밀번호 암호화 정책을 설정한다.
@Autowired
private UserDetailsService userDetailsService;
...
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
4-5-3 인증 관리자에 인증방식을 in-Memory에서 DaoAuthenticationProvider로 설정한다.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
// UserBuilder builder = User.builder();
//
// auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
// .withUser(builder.username("pilseong").password("$2a$10$UwsYjhu/iNCKbRDEsYpoi.AvuQlxX1yv/9TbtEmnmbgFQkh4z0TWa").roles("EMPLOYEE"))
// .withUser(builder.username("suel").password("$2a$10$F1kAcy7iAw0790oaf4ATxeerP779yfrK.hncxhfU1jDoSLS.drNem").roles("EMPLOYEE", "MANAGER"))
// .withUser(builder.username("noel").password("$2a$10$G59j5AkWAujaRfp2AhJtPeoirdMiPlfYkEoVczYpwTiWXeWyHadPS").roles("EMPLOYEE", "ADMIN"));
}
5. 로그인 성공
6. 이 포스트에서 한 내용은
6-1 중복 유저가 있으면 그 내용을 화면에 표출해 주는 부분과
6-2 DaoAuthenticationProvider를 이용한 스프링 Security 로그인 설정이다.
6-3 사용자가 임의로 설계한 데이터베이스을 사용하여 Spring Security 로그인을 이용하는 방법이다.
'Spring > Spring Advanced' 카테고리의 다른 글
- Total
- Today
- Yesterday
- 도커 개발환경 참고
- AWS ARN 구조
- Immuability에 관한 설명
- 자바스크립트 멀티 비동기 함수 호출 참고
- WSDL 참고
- SOAP 컨슈머 참고
- MySql dump 사용법
- AWS Lambda with Addon
- NFC 드라이버 linux 설치
- electron IPC
- mifare classic 강의
- go module 관련 상세한 정보
- C 메모리 찍어보기
- C++ Addon 마이그레이션
- JAX WS Header 관련 stackoverflow
- SOAP Custom Header 설정 참고
- SOAP Custom Header
- SOAP BindingProvider
- dispatcher 사용하여 설정
- vagrant kvm으로 사용하기
- git fork, pull request to the …
- vagrant libvirt bridge network
- python, js의 async, await의 차이
- go JSON struct 생성
- Netflix Kinesis 활용 분석
- docker credential problem
- private subnet에서 outbound IP 확…
- 안드로이드 coroutine
- kotlin with, apply, also 등
- 안드로이드 초기로딩이 안되는 경우
- navigation 데이터 보내기
- 레이스 컨디션 navController
- raylib
- jsp
- Security
- 하이버네이트
- Validation
- form
- 매핑
- Rest
- Many-To-Many
- 설정
- one-to-one
- hibernate
- Spring
- mapping
- 스프링
- XML
- Spring Security
- 외부파일
- login
- 스프링부트
- one-to-many
- RestTemplate
- crud
- 설정하기
- MYSQL
- Angular
- WebMvc
- 자바
- 상속
- spring boot
- 로그인