참조한 강의 : www.udemy.com/course/spring-hibernate-tutorial/
- Spring Security & JDBC
앞의 포스팅까지는 DB 와 연동하는게 아니라, 그냥 스프링 앱 메모리에 유저 데이터를 저장해서 인증하는 방식이었는데, 당연히 실제 업무나 상품으로써 내놓을려면 DB 에 저장해놓는게 일반적이다.
그래서 이번 포스팅에서는 DB 와 Spring Security 를 연동하는 방법과 BCrypt 를 이용해서 암호화하여 비밀번호를 저장하는 방법 그리고 회원가입을 만드는 방법에 대해 알아본다.
먼저 DB 와 Spring Security 를 연동하는 방법에 대해 알아본다
Spring Security 와 DB 를 연동할때 주의할점은, Spring Security 가 기본값으로 사용하는, 테이블과 칼럼명이 존재한다.
예를들면 아래 그림 같은 것이 있다.
DB 에 스키마를 정의할때 반드시 위의 칼럼명과 테이블명이 똑같아야 한다.
그리고 DB 에 비밀번호를 저장할때는 정해진 형식이 있다.
{id}encodedPassword
이런식으로 형식을 잡아놓고 집어넣어야 한다
{id} 에 들어가는 값으로는 아래와 같은게 있다.
1) noop
: 암호화를 전혀 하지 않고 plain text 로 넣는 방식
2) bcrypt
: BCrypt 암호화 방식을 이용해서 넣는 방식
비밀번호를 암호화할때는 보통 BCrypt 암호화 방식을 많이 사용한다.
그러면, 먼저 SQL 코드를 작성해서, 테이블을 만들고 샘플 데이터를 몇개 넣어보겠다.
- sql
DROP DATABASE IF EXISTS `spring_security_demo_plaintext`;
CREATE DATABASE IF NOT EXISTS `spring_security_demo_plaintext`;
USE `spring_security_demo_plaintext`;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `users`
--
INSERT INTO `users`
VALUES
('john','{noop}test123',1),
('mary','{noop}test123',1),
('susan','{noop}test123',1);
--
-- Table structure for table `authorities`
--
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `authorities_idx_1` (`username`,`authority`),
CONSTRAINT `authorities_ibfk_1` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Inserting data for table `authorities`
--
INSERT INTO `authorities`
VALUES
('john','ROLE_EMPLOYEE'),
('mary','ROLE_EMPLOYEE'),
('mary','ROLE_MANAGER'),
('susan','ROLE_EMPLOYEE'),
('susan','ROLE_ADMIN');
|
cs |
다음으로 스프링 프로젝트가 DB 와 연결할 수 있도록 pom.xml 파일에 JDBC, c3p0 를 설정해야 한다.
- pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>${c3po.version}</version>
</dependency>
|
cs |
위의 mysql-connector-java 는 JDBC 를 의미하고, c3p0 는 DB Connection Pool 을 의미한다.
다음으로는, 스프링이 DB 에 접속할 수 있도록 driver, db url, username, password 등을 제공해야한다.
보통 이런 민감한 정보들은 별도의 파일로 관리하는데
(node.js 기반의 웹 개발을 해봤다면 .env 파일을 다룬적이 있을 것이다. 이거와 똑같다)
여기서는 .properties 라는 확장자명을 가진 파일로 관리한다.
이 파일을 저장하는 위치는 아래와 같이 지정한다
- persistence-mysql.properties
#
# JDBC connection properties
#
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/DBURL?useSSL=false&serverTimezone=UTC
jdbc.user=username
jdbc.password=password
#
# Connection pool properties
#
connection.pool.initialPoolSize=5
connection.pool.minPoolSize=5
connection.pool.maxPoolSize=20
connection.pool.maxIdleTime=3000
|
cs |
다음으로 스프링 설정 파일이 properties 파일을 읽어들일 수 있도록 설정해줘야한다.
이때는 @PropertySource 라는 어노테이션을 사용한다.
그리고 properties 에서 읽어들인 값을 저장하기 위한 별도의 객체가 필요한데, spring 에서는 이를 위한 Environment 라는 객체를 제공하고 있다.
- DemoAppConfig.java
@Configuration
@EnableWebMvc
@ComponentScan(basePackages="com.luv2code.springsecurity.demo")
@PropertySource("classpath:persistence-mysql.properties")
public class DemoAppConfig implements WebMvcConfigurer {
// set up variable to hold the properties
@Autowired
private Environment env;
// set up a logger for diagnostics
private Logger logger = Logger.getLogger(getClass().getName());
// define a bean for our security datasource
@Bean
public DataSource securityDataSource() {
// create connection pool
ComboPooledDataSource securityDataSource = new ComboPooledDataSource();
// set the jdbc driver
try {
securityDataSource.setDriverClass("com.mysql.jdbc.Driver");
}
catch (PropertyVetoException exc) {
throw new RuntimeException(exc);
}
// for sanity's sake, let's log url and user ... just to make sure we are reading the data
logger.info("jdbc.url=" + env.getProperty("jdbc.url"));
logger.info("jdbc.user=" + env.getProperty("jdbc.user"));
// set database connection props
securityDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
securityDataSource.setUser(env.getProperty("jdbc.user"));
securityDataSource.setPassword(env.getProperty("jdbc.password"));
// set connection pool props
securityDataSource.setInitialPoolSize(
getIntProperty("connection.pool.initialPoolSize"));
securityDataSource.setMinPoolSize(
getIntProperty("connection.pool.minPoolSize"));
securityDataSource.setMaxPoolSize(
getIntProperty("connection.pool.maxPoolSize"));
securityDataSource.setMaxIdleTime(
getIntProperty("connection.pool.maxIdleTime"));
return securityDataSource;
}
// need a helper method
// read environment property and convert to int
private int getIntProperty(String propName) {
String propVal = env.getProperty(propName);
// now convert to int
int intPropVal = Integer.parseInt(propVal);
return intPropVal;
}
}
|
cs |
@PropertySource 에서 classpath 의 기본 경로는, src/main/resources 이다.
그리고 Connection Pool 을 만들기 위한 객체로 ComboPooledDataSource 라는 객체가 사용되었다.
그 밑의 try catch 구문은 딱히 설명하지 않더라도 직관적으로 이해가된다.
그리고 아래의 getIntProperty 메소드는, connection pool 관련해서 properties 파일에서 값을 얻어올때, 설정값은 숫자로 넣었지만 Environment 객체가 읽어들이는 값은 문자열로 인식되기 때문에, 이 값을 숫자로 변환하는 과정이 필요하다. 이를 위해서 넣어준 메소드이다.
다음으로 Spring Security Config 파일을 수정해야한다.
이전까지는 in-memory 방식이었으므로, 그 코드들을 지우고, DB 와 연결하도록 수정해야한다.
- DemoSecurityConfig.java
@Configuration
@EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource securityDataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(securityDataSource);
}
}
|
cs |
DataSource 는 javax.sql.DataSource 에서 import 해야한다.
(DataSource ? : deepweller.tistory.com/6)
여기까지 하면 DB 와 연동을 하는 기본작업을 끝낸것이다.
- Password Encryption
: 다음으로 비밀번호를 암호화 하여 DB 에 저장하는 방법에 대해 알아보자
위에서 한 예제는 당연히 비밀번호를 전혀 암호화 하지 않고 저장했기 때문에, 보안상의 문제가 반드시 발생할 가능성이 있다.
일반적으로 비밀번호 암호화 할때 가장 많이 쓰는게 BCrypt 해싱 방식 이다.
BCrypt 는 단방향 해시 함수를 사용하는데, 단방향 해시 함수는 같은 평문을 넣더라도 해싱함수를 거칠때 마다 매번 다른 해시값이 나오게된다. BCrypt 는 해시 함수에 추가적으로 Salting 과 Key Stretching 라는것을 이용해서 만든 암호화 방식이다
자세한 사항은 아래 참조
Spring Security 에 이 BCrypt 를 어떻게 적용시키는지 알아보자
먼저 위의 예제와는 다르게 DB 의 칼럼을 약간 수정할 필요가 있다.
password 부분의 최소 길이가 68 글자여야한다.
{bcrypt} 라는 키워드가 앞에 붙어서 8글자
해시값 60글자가 붙어서 최소 68 글자 이상 붙도록 password 칼럼에 대한 수정이 필요하다.
(위의 예제에선 50글자로 해놨다)
- User Registration
: 다음으로 회원가입 방법에 대해 알아본다
먼저 스키마를 아래 그림처럼 약간 바꿀 것이다
- sql
DROP DATABASE IF EXISTS `spring_security_custom_user_demo`;
CREATE DATABASE IF NOT EXISTS `spring_security_custom_user_demo`;
USE `spring_security_custom_user_demo`;
--
-- Table structure for table `user`
--
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` char(80) NOT NULL,
`first_name` varchar(50) NOT NULL,
`last_name` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `user`
--
-- NOTE: The passwords are encrypted using BCrypt
--
-- A generation tool is avail at: http://www.luv2code.com/generate-bcrypt-password
--
-- Default passwords here are: fun123
--
INSERT INTO `user` (username,password,first_name,last_name,email)
VALUES
('john','$2a$04$eFytJDGtjbThXa80FyOOBuFdK2IwjyWefYkMpiBEFlpBwDH.5PM0K','John','Doe','john@luv2code.com'),
('mary','$2a$04$eFytJDGtjbThXa80FyOOBuFdK2IwjyWefYkMpiBEFlpBwDH.5PM0K','Mary','Public','mary@luv2code.com'),
('susan','$2a$04$eFytJDGtjbThXa80FyOOBuFdK2IwjyWefYkMpiBEFlpBwDH.5PM0K','Susan','Adams','susan@luv2code.com');
--
-- Table structure for table `role`
--
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `roles`
--
INSERT INTO `role` (name)
VALUES
('ROLE_EMPLOYEE'),('ROLE_MANAGER'),('ROLE_ADMIN');
--
-- Table structure for table `users_roles`
--
DROP TABLE IF EXISTS `users_roles`;
CREATE TABLE `users_roles` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `FK_ROLE_idx` (`role_id`),
CONSTRAINT `FK_USER_05` FOREIGN KEY (`user_id`)
REFERENCES `user` (`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_ROLE` FOREIGN KEY (`role_id`)
REFERENCES `role` (`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
SET FOREIGN_KEY_CHECKS = 1;
--
-- Dumping data for table `users_roles`
--
INSERT INTO `users_roles` (user_id,role_id)
VALUES
(1, 1),
(2, 1),
(2, 2),
(3, 1),
(3, 3)
|
cs |
테이블과 샘플 데이터 몇개를 넣어줬다.
다음으로 hibernate validator, spring transaction, hibernate ORM 등을 사용하기 위해서 pom.xml 에 의존성 주입을 해준다
- pom.xml
<dependencies>
<!-- Hibernate ORM -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
<!-- Spring Transaction -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- Spring ORM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- Hibernate Core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
|
cs |
그리고 프로젝트의 구조를 레이어드 아키텍처 기반의 구조로 만들 것이므로, DAO, Service, Controller 의 3단계로 구성할 것이다.
그리고, ORM 을 위해서 Entity 클래스도 따로 만들어 줄 것이며, Validation 을 위한 자체적인 어노테이션도 몇개 제작한다.
먼저 spring security config 파일에 spring security 가 기본적으로 제공하는 BCryptPasswordEncoder 클래스를 Bean 으로 선언해서 Spring 이 관리하도록 해주고, 레이어드 아키텍쳐를 거쳐서 마지막 DAO 를 통해 DB 에 암호를 저장할때 암호화한 상태로 저장할 수 있도록 DaoAuthenticationProvider 를 Bean 으로 선언하여 관리한다,
- DemoSecurityConfig.java
@Configuration
@EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {
// add a reference to our security data source
@Autowired
private UserService userService;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService); //set the custom user details service
auth.setPasswordEncoder(passwordEncoder()); //set the password encoder - bcrypt
return auth;
}
}
|
cs |
다음으로 회원가입시에 유저가 입력한 내용에 대해서 데이터 유효성 검증을 위한 코드를 넣는다.
- CrmUser.java
@FieldMatch.List({@FieldMatch(first = "password", second = "matchingPassword", message = "The Password Fields must match")})
public class CrmUser {
@NotNull(message="is required")
@Size(min=1, message="is required")
private String userName;
@NotNull(message="is required")
@Size(min=1, message="is required")
private String password;
@NotNull(message="is required")
@Size(min=1, message="is required")
private String matchingPassword;
@NotNull(message="is required")
@Size(min=1, message="is required")
private String firstName;
@NotNull(message="is required")
@Size(min=1, message="is required")
private String lastName;
@ValidEmail
@NotNull(message="is required")
@Size(min=1, message="is required")
private String email;
// getter, setter, constructor 생략
}
|
cs |
@FieldMatch 와 @ValidEmail 은 스프링이 제공하지 않는 자체적으로 만든 어노테이션이다.
어노테이션은 interface 기반이므로, 선언과 정의를 따로 해야한다
- @FieldMatch Interface
@Constraint(validatedBy = FieldMatchValidator.class)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FieldMatch {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
FieldMatch[] value();
}
}
|
cs |
- @FieldMatch Implementation
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
private String message;
@Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
message = constraintAnnotation.message();
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
boolean valid = true;
try {
final Object firstObj = new BeanWrapperImpl(value).getPropertyValue(firstFieldName);
final Object secondObj = new BeanWrapperImpl(value).getPropertyValue(secondFieldName);
valid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
}
catch (final Exception ignore) {
// we can ignore
}
if (!valid) {
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(firstFieldName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
return valid;
}
}
|
cs |
- @ValidEmail Interface
@Constraint(validatedBy=EmailValidator.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidEmail {
String message() default "Invalid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
|
cs |
- @ValidEmail Implementation
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
private Pattern pattern;
private Matcher matcher;
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
@Override
public boolean isValid(final String email, final ConstraintValidatorContext context) {
pattern = Pattern.compile(EMAIL_PATTERN);
if (email == null) {
return false;
}
matcher = pattern.matcher(email);
return matcher.matches();
}
}
|
cs |
다음으로 hibernate 가 스캔할 패키지명과 연동될 SQL 종류를 선정해야한다
기존에 만들었던 persistence-mysql.properties 에 다음의 내용을 추가한다
- persistence-mysql.properties
# Hibernate properties
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hiberante.packagesToScan=com.luv2code.springsecurity.demo.entity
|
cs |
다음으로, Hibernate ORM 이 DB 와 연동할 수 있도록 Entity 클래스를 선언한다
- User.java
@Entity
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="username")
private String userName;
@Column(name="password")
private String password;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
@Column(name="email")
private String email;
@ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinTable(name="users_roles", joinColumns=@JoinColumn(name="user_id"), inverseJoinColumns=@JoinColumn(name="role_id"))
private Collection<Role> roles;
// getter, setter 등 생략..
}
|
cs |
- Role.java
@Entity
@Table(name="role")
public class Role {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="name")
private String name;
// getter, setter 등 생략..
}
|
cs |
다음으로, jsp 에 회원가입 버튼과 회원가입을 위한 form 화면을 만든다
- login.jsp
<!-- 나머지 로그인 부분 생략... -->
<div>
<a href="${pageContext.request.contextPath}/register/showRegistrationForm" class="btn btn-primary" role="button" aria-pressed="true">Register New User</a>
</div>
|
cs |
- registration-form.jsp
<!-- taglib 생략.. -->
<!doctype html>
<html lang="en">
<head>
<!-- css, bootstrap 부분 생략... -->
</head>
<body>
<div>
<div id="loginbox" style="margin-top: 50px;"
class="mainbox col-md-3 col-md-offset-2 col-sm-6 col-sm-offset-2">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="panel-title">Register New User</div>
</div>
<div style="padding-top: 30px" class="panel-body">
<!-- Registration Form -->
<form:form action="${pageContext.request.contextPath}/register/processRegistrationForm"
modelAttribute="crmUser"
class="form-horizontal">
<!-- Place for messages: error, alert etc ... -->
<div class="form-group">
<div class="col-xs-15">
<div>
<!-- Check for registration error -->
<c:if test="${registrationError != null}">
<div class="alert alert-danger col-xs-offset-1 col-xs-10">
${registrationError}
</div>
</c:if>
</div>
</div>
</div>
<!-- User name -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<form:errors path="userName" cssClass="error" />
<form:input path="userName" placeholder="username (*)" class="form-control" />
</div>
<!-- Password -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<form:errors path="password" cssClass="error" />
<form:password path="password" placeholder="password (*)" class="form-control" />
</div>
<!-- Confirm Password -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<form:errors path="matchingPassword" cssClass="error" />
<form:password path="matchingPassword" placeholder="confirm password (*)" class="form-control" />
</div>
<!-- First name -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<form:errors path="firstName" cssClass="error" />
<form:input path="firstName" placeholder="first name (*)" class="form-control" />
</div>
<!-- Last name -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<form:errors path="lastName" cssClass="error" />
<form:input path="lastName" placeholder="last name (*)" class="form-control" />
</div>
<!-- Email -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<form:errors path="email" cssClass="error" />
<form:input path="email" placeholder="email (*)" class="form-control" />
</div>
<!-- Register Button -->
<div style="margin-top: 10px" class="form-group">
<div class="col-sm-6 controls">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</div>
</form:form>
</div>
</div>
</div>
</div>
</body>
</html>
|
cs |
- registration-confirmation.jsp
<!doctype html>
<html>
<head>
<title>Registration Confirmation</title>
</head>
<body>
<h2>User registered successfully!</h2>
<hr>
<a href="${pageContext.request.contextPath}/showMyLoginPage">Login with new user</a>
</body>
</html>
|
cs |
다음으로 컨트롤러를 만든다.
- RegistrationController.java
@Controller
@RequestMapping("/register")
public class RegistrationController {
@Autowired
private UserService userService;
private Logger logger = Logger.getLogger(getClass().getName());
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
@GetMapping("/showRegistrationForm")
public String showMyLoginPage(Model model) {
model.addAttribute("crmUser" ,new CrmUser());
return "registration-form";
}
@PostMapping("/processRegistrationForm")
public String processRegistrationForm(@Valid @ModelAttribute("crmUser") CrmUser crmUser, BindingResult bindingResult, Model model) {
String userName = crmUser.getUserName();
logger.info("Processing registration form for : " + userName);
if (bindingResult.hasErrors()) {
return "registration-form";
}
User existingUser = userService.findByUserName(userName);
if (existingUser != null) {
model.addAttribute("crmUser", new CrmUser());
model.addAttribute("registrationError", "User name already exists");
logger.warning("User name already exists");
return "registration-form";
}
userService.save(crmUser);
logger.info("Successfully created user" + userName);
return "registration-confirmation";
}
}
|
cs |
다음으로 Service 객체와 DAO 객체를 만든다.
- UserService.java
public interface UserService extends UserDetailsService {
User findByUserName(String userName);
void save(CrmUser crmUser);
}
|
cs |
- UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
// need to inject user dao
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
@Transactional
public User findByUserName(String userName) {
// check the database if the user already exists
return userDao.findByUserName(userName);
}
@Override
@Transactional
public void save(CrmUser crmUser) {
User user = new User();
// assign user details to the user object
user.setUserName(crmUser.getUserName());
user.setPassword(passwordEncoder.encode(crmUser.getPassword()));
user.setFirstName(crmUser.getFirstName());
user.setLastName(crmUser.getLastName());
user.setEmail(crmUser.getEmail());
// give user default role of "employee"
user.setRoles(Arrays.asList(roleDao.findRoleByName("ROLE_EMPLOYEE")));
// save user in the database
userDao.save(user);
}
@Override
@Transactional
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userDao.findByUserName(userName);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
}
private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
}
|
cs |
- UserDao.java
public interface UserDao {
User findByUserName(String userName);
void save(User user);
}
|
cs |
- UserDaoImpl.java
@Repository
public class UserDaoImpl implements UserDao {
// need to inject the session factory
@Autowired
private SessionFactory sessionFactory;
@Override
public User findByUserName(String theUserName) {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// now retrieve/read from database using username
Query<User> theQuery = currentSession.createQuery("from User where userName=:uName", User.class);
theQuery.setParameter("uName", theUserName);
User theUser = null;
try {
theUser = theQuery.getSingleResult();
} catch (Exception e) {
theUser = null;
}
return theUser;
}
@Override
public void save(User theUser) {
// get current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// create the user ... finally LOL
currentSession.saveOrUpdate(theUser);
}
}
|
cs |
- RoleDao.java
public interface RoleDao {
public Role findRoleByName(String roleName);
}
|
cs |
- RoleDaoImpl.java
@Repository
public class RoleDaoImpl implements RoleDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public Role findRoleByName(String roleName) {
Session currentSession = sessionFactory.getCurrentSession();
Query<Role> query = currentSession.createQuery("from Role where name=:roleName", Role.class);
query.setParameter("roleName", roleName);
Role role = null;
try {
role = query.getSingleResult();
} catch (Exception e) {
role = null;
e.printStackTrace();
}
return role;
}
}
|
cs |
여기 까지하면 기본적인 틀은 다 갖췄다.
아래는 실행화면 이다
- References)
1. DataSource : deepweller.tistory.com/6
2. BCrypt : velog.io/@sungjun-jin/bcrypt
'Spring' 카테고리의 다른 글
Spring REST - REST Controller (0) | 2021.03.11 |
---|---|
Spring REST - Overview (0) | 2021.03.07 |
Spring Security - User Roles (0) | 2021.03.06 |
Spring Security - CSRF (Cross Site Request Forgery) (0) | 2021.03.06 |
Spring Security - Custom Login Form (0) | 2021.03.05 |