본문 바로가기
Web developer/Spring

[Spring] 스프링 시큐리티 로그인 기능 구현해보기

by doongjun 2021. 8. 24.

스프링 시큐리티를 사용하여 로그인 기능을 구현해보기 전, 이전 포스팅을 보고 온다면 더 이해가 쉬울 것이다.

2021.08.21 - [WEB developer/Spring] - [Spring] 스프링 시큐리티 이해하기

 

[Spring] 스프링 시큐리티 이해하기

스프링 시큐리티란? 스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증과 권한)을 담당하는 프레임워크이다. 만약 시큐리티를 사용하지 않으면 자체적으로 세션을 체크하고 redirect 등

doongjun.tistory.com

먼저 스프링 시큐리티 기본 설정을 해야할 텐데, 아래 포스팅의 기본 설정들이 되어있다는 가정 하에 진행된다.

2021.08.20 - [WEB developer/Spring] - [Spring] 기본 설정 + Spring Security 설정

 

[Spring] 기본 설정 + Spring Security 설정

새로운 프로젝트를 생성할 때마다 해줘야 하는 기본설정들을 기억하기 위해 작성하는 글이다. web.xml 설정이 아닌 자바 설정이고 Spring Security 설정 또한 포함하여 작성하였다. 더보기 pom.xml UTF-8 1

doongjun.tistory.com

 

1. 데이터베이스 모델링, 테이블 생성, 데이터 추가

drop table if exists member_role;
drop table if exists member;


-- -----------------------------------------------------
-- Table `member`
-- -----------------------------------------------------
CREATE TABLE `member` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'member id',
  `name` VARCHAR(255) NOT NULL COMMENT 'member name',
  `password` VARCHAR(255) NOT NULL COMMENT '암호회된 password',
  `email` VARCHAR(255) NOT NULL UNIQUE COMMENT 'login id, email',
  `create_date` DATETIME NULL DEFAULT NULL COMMENT '등록일',
  `modify_date` DATETIME NULL DEFAULT NULL COMMENT '수정일',
  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- -----------------------------------------------------
-- Table `member_role`
-- -----------------------------------------------------
CREATE TABLE `member_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'role id',
  `member_id` INT(11) NOT NULL COMMENT 'member id fk',
  `role_name` VARCHAR(100) NOT NULL COMMENT 'role 이름 ROLE_ 로 시작하는 값이어야 한다.',
  PRIMARY KEY (`id`),
  FOREIGN KEY (`member_id`)
  REFERENCES `member` (`id`)
)  ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into member (id, name, password, email, create_date, modify_date) values ( 1, '김동준', '$2a$10$G/ADAGLU3vKBd62E6GbrgetQpEKu2ukKgiDR5TWHYwrem0cSv6Z8m', 'carami@example.com', now(), now());
insert into member (id, name, password, email, create_date, modify_date) values ( 2, '배다연', '$2a$10$G/ADAGLU3vKBd62E6GbrgetQpEKu2ukKgiDR5TWHYwrem0cSv6Z8m', 'toto@example.com', now(), now());

insert into member_role (id, member_id, role_name) values (1, 1, 'ROLE_USER');
insert into member_role (id, member_id, role_name) values (2, 1, 'ROLE_ADMIN');

insert into member_role (id, member_id, role_name) values (3, 2, 'ROLE_USER');

 

2. 데이터베이스로부터 읽어들이기 위한 DTO와 DAO작성

Member.java

import java.util.Date;

public class Member {
	private Long id;
	private String name;
	private String password;
	private String email;
	private Date createDate;
	private Date modifyDate;
	
	public Member() {
		createDate = new Date();
		modifyDate = new Date();
	}
	
	public Member(Long id, String name, String password, String email) {
		this();
		this.name = name;
		this.password = password;
		this.email = email;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getCreateDate() {
		return createDate;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	public Date getModifyDate() {
		return modifyDate;
	}

	public void setModifyDate(Date modifyDate) {
		this.modifyDate = modifyDate;
	}
}

 

 

email정보와 일치하는 회원 정보를 읽는 Sql 클래스 작성

MemberDaoSqls.java

public class MemberDaoSqls {
	public static final String SELECT_ALL_BY_EMAIL = "SELECT id, name, password, email, create_date, modify_date FROM member WHERE email = :email";
}

 

 

MemberDao클래스 작성

MemberDao.java

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.edwith.webbe.securityexam.dto.Member;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class MemberDao {
	private NamedParameterJdbcTemplate jdbc;
	// BeanPropertyRowMapper는 Role클래스의 프로퍼티를 보고 자동으로 칼럼과 맵핑해주는 RowMapper객체를 생성한다.
	// roleId 프로퍼티는 role_id 칼럼과 맵핑이 된다.
	// 만약 프로퍼티와 칼럼의 규칙이 맞아 떨어지지 않는다면 직접 RowMapper객체를 생성해야 한다.
	// 생성하는 방법은 아래의 rowMapper2를 참고한다.
	private RowMapper<Member> rowMapper = BeanPropertyRowMapper.newInstance(Member.class);
	
	public MemberDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
	}
	
	public Member getMemberByEmail(String email) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("email", email);
		
		return jdbc.queryForObject(MemberDaoSqls.SELECT_ALL_BY_EMAIL, map, rowMapper);
	}
}

 

회원의 권한(Role)정보를 저장하기 위한 MemberRole DTO클래스 작성

MemberRole.java

public class MemberRole {
	private Long id;
	private Long memberId;
	private String roleName;
	
	public MemberRole() {
	}
	
	public MemberRole(Long memberId, String roleName) {
		this.memberId = memberId;
		this.roleName = roleName;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Long getMemberId() {
		return memberId;
	}

	public void setMemberId(Long memberId) {
		this.memberId = memberId;
	}

	public String getRoleName() {
		return roleName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
}

 

email에 해당하는 권한 정보를 읽어들이기 위해서 member 테이블과 member_role 테이블을 JOIN하여 결과를 얻는 Sql을 가진 MemberRoleDaoSqls 클래스 작성

MemberRoleDaoSqls.java

public class MemberRoleDaoSqls {
	public static final String SELECT_ALL_BY_EMAIL = 
			"SELECT mr.id, mr.member_id, mr.role_name "
			+ "FROM member_role mr "
			+ "JOIN member m ON mr.member_id = m.id "
			+ "WHERE m.email = :email";
}

 

권한 정보를 읽어들이는 MemberRoleDao 클래스 작성

MemberRoleDao.java

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.edwith.webbe.securityexam.dto.MemberRole;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class MemberRoleDao {
	private NamedParameterJdbcTemplate jdbc;
	// BeanPropertyRowMapper는 Role클래스의 프로퍼티를 보고 자동으로 칼럼과 맵핑해주는 RowMapper객체를 생성한다.
	// roleId 프로퍼티는 role_id 칼럼과 맵핑이 된다.
	// 만약 프로퍼티와 칼럼의 규칙이 맞아 떨어지지 않는다면 직접 RowMapper객체를 생성해야 한다.
	// 생성하는 방법은 아래의 rowMapper2를 참고한다.
	private RowMapper<MemberRole> rowMapper = BeanPropertyRowMapper.newInstance(MemberRole.class);
	
	public MemberRoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
	}
	
	public List<MemberRole> getRolesByEmail(String email){
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("email", email);
		return jdbc.query(MemberRoleDaoSqls.SELECT_ALL_BY_EMAIL, map, rowMapper);
	}
}

 

데이터베이스 접속, MemberDao, MemberRoleDao클래스가 알맞게 동작하는지 확인하기 위해 테스트 클래스를 작성한다.

MemberDaoTest.java

import java.sql.Connection;

import javax.sql.DataSource;

import org.edwith.webbe.securityexam.config.ApplicationConfig;
import org.edwith.webbe.securityexam.dto.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ApplicationConfig.class})
public class MemberDaoTest {
	@Autowired
	DataSource dataSource;
	
	@Autowired
	MemberDao memberDao;
	
	@Autowired
	MemberRoleDao memberRoleDao;
	
	@Test
	public void configTest() throws Exception{
		// 아무 작업도 하지 않는다. 실행이 잘된다는 것은 Spring 설정이 잘 되어 있다는 것을 의미한다.
	}
	
	@Test
	public void connectionTest() throws Exception{
		Connection connection = dataSource.getConnection();
		Assert.assertNotNull(connection);
	}
	
	@Test
	public void getUser() throws Exception{
		Member member = memberDao.getMemberByEmail("carami@example.com");
		Assert.assertNotNull(member);
		Assert.assertEquals("김동준", member.getName());
	}
}

 

3. Login/Logout 처리를 위한 클래스 작성하기

1. 먼저 로그인 아이디와 암호 정보를 갖고 있는 UserEntity 클래스를 생성한다.

UserEntity.java

public class UserEntity {
	private String loginUserId;
	private String password;
	
	public UserEntity(String loginUserId, String password) {
		this.loginUserId = loginUserId;
		this.password = password;
	}

	public String getLoginUserId() {
		return loginUserId;
	}

	public void setLoginUserId(String loginUserId) {
		this.loginUserId = loginUserId;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

 

2. 로그인 아이디와 권한(Role) 정보를 갖는 UserRoleEntity 클래스를 생성한다.

UserRoleEntity.java

public class UserRoleEntity {
	private String userLoginId;
	private String roleName;
	
	public UserRoleEntity(String userLoginId, String roleName) {
		this.userLoginId = userLoginId;
		this.roleName = roleName;
	}

	public String getUserLoginId() {
		return userLoginId;
	}

	public void setUserLoginId(String userLoginId) {
		this.userLoginId = userLoginId;
	}

	public String getRoleName() {
		return roleName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
	
}

 

3. 로그인한 사용자의 ID를 파라미터로 받아들여서 UserEntity와 List<UserRoleEntity>를 리턴하는 메소드를 가지고 있는 UserDbService 인터페이스를 작성한다.

로그인 정보가 저장된 데이터베이스의 구조는 프로젝트마다 다르기 때문에 해당 인터페이스를 구현해야한다.

UserDbService.java

import java.util.List;

public interface UserDbService {
	public UserEntity getUser(String loginUserId);
	public List<UserRoleEntity> getUserRoles(String loginUserId);
}

 

4. 데이터베이스에서 읽어 들인 로그인 정보는 UserDetails인터페이스를 구현하고 있는 객체에 저장되어야 한다. UserDetails를 구현하고 있는 CustomUserDetails 클래스를 생성한다.

CustomUserDetails.java

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class CustomUserDetails implements UserDetails{
	private String username;
	private String password;
	private boolean isEnabled;
	private boolean isAccountNonExpired;
	private boolean isAccountNonLocked;
	private boolean isCredentialsNonExpired;
	private Collection<?extends GrantedAuthority>authorities;
	
    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }

    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        isAccountNonExpired = accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return isAccountNonLocked;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        isAccountNonLocked = accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        isCredentialsNonExpired = credentialsNonExpired;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
}

 

5. UserDetailsService 인터페이스를 구현하는 CustomUserDetailsService를 생성한다.

CustomUserDetailsService.java

import java.util.ArrayList;
import java.util.List;

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.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {
	// CustomUserDbService는 인터페이스다. 해당 인터페이스를 구현하고 있는 객체가 Bean으로 등록되어 있어야 한다.
	@Autowired
	UserDbService userDbService;
	
	@Override
	public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
		// loginId에 해당하는 정보를 데이터베이스에서 읽어 CustomUser객체에 저장한다.
		UserEntity customUser = userDbService.getUser(loginId);
		if(customUser == null)
			throw new UsernameNotFoundException("사용자가 입력한 아이디에 해당하는 사용자를 찾을 수 없습니다.");
		
		// 해당 정보를 CustomUserDetails객체에 저장한다.
		CustomUserDetails customUserDetails = new CustomUserDetails();
		customUserDetails.setUsername(customUser.getLoginUserId());
		customUserDetails.setPassword(customUser.getPassword());
		
		List<UserRoleEntity> customRoles = userDbService.getUserRoles(loginId);
		
		//로그인한 사용자의 권한 정보를 GrantedAuthority를 구현하고 있는 SimpleGrantedAuthority객체에 담아 리스트에 추가한다.
		//MemberRole 이름은 "ROLE_"로 시작되어야 한다.
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		if(customRoles != null) {
			for(UserRoleEntity customRole : customRoles) {
				authorities.add(new SimpleGrantedAuthority(customRole.getRoleName()));
			}
		}
		
		//CustomUserDetails 객체에 권한 목록 (authorities)를 설정한다.
		customUserDetails.setAuthorities(authorities);
		customUserDetails.setEnabled(true);
		customUserDetails.setAccountNonExpired(true);
		customUserDetails.setAccountNonLocked(true);
		customUserDetails.setCredentialsNonExpired(true);
		return customUserDetails;
	}
}

 

6. UserDbService 인터페이스를 상속받는 MemberService 인터페이스를 작성한다.

UserDbService는 스프링 시큐리티에서 필요로 하는 정보를 갖고 오는 메소드를 갖고 있다. MemberService는 회원과 관련된 모든 정보를 처리하는 서비스이다(회원 등록등..).

MemberService.java

import org.edwith.webbe.securityexam.service.security.UserDbService;

public interface MemberService extends UserDbService{

}

 

7. MemberServiceImpl클래스는 MemberService인터페이스를 구현한다.

MemberService인터페이스를 구현한다는 것은 UserDbService 역시 구현해야한다는 것이다.

MemberServiceImpl.java

import java.util.ArrayList;
import java.util.List;

import org.edwith.webbe.securityexam.dao.MemberDao;
import org.edwith.webbe.securityexam.dao.MemberRoleDao;
import org.edwith.webbe.securityexam.dto.Member;import org.edwith.webbe.securityexam.dto.MemberRole;
import org.edwith.webbe.securityexam.service.security.UserEntity;
import org.edwith.webbe.securityexam.service.security.UserRoleEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberServiceImpl implements MemberService {
    // 생성자에 위해 주입되는 객체이고, 해당 객체를 초기화할 필요가 이후에 없기 때문에 final로 선언하였다.
    // final로 선언하고 초기화를 안한 필드는 생성자에서 초기화를 해준다.
	private MemberDao memberDao;
	private MemberRoleDao memberRoleDao;
	
	// @Service가 붙은 객체는 스프링이 자동으로 Bean으로 생성하는데
    // 기본생성자가 없고 아래와 같이 인자를 받는 생성자만 있을 경우 자동으로 관련된 타입이 Bean으로 있을 경우 주입해서 사용하게 된다.
	public MemberServiceImpl(MemberDao memberDao, MemberRoleDao memberRoleDao) {
		this.memberDao = memberDao;
		this.memberRoleDao = memberRoleDao;
	}

	@Override
	@Transactional
	public UserEntity getUser(String loginUserId) {
		Member member = memberDao.getMemberByEmail(loginUserId);
		return new UserEntity(member.getEmail(), member.getPassword());
	}

	@Override
	@Transactional
	public List<UserRoleEntity> getUserRoles(String loginUserId) {
		List<MemberRole> memberRoles = memberRoleDao.getRolesByEmail(loginUserId);
		List<UserRoleEntity> list = new ArrayList<>();
		
		for(MemberRole memberRole : memberRoles) {
			list.add(new UserRoleEntity(loginUserId, memberRole.getRoleName()));
		}

		return list;
	}
}

 

4. 로그인, 로그아웃 처리를 위한 설정 수정

기존의 SecurityConfig.java 파일을 아래와 같이 수정

SecurityConfig.java

import org.edwith.webbe.securityexam.service.security.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.authentication.AuthenticationManagerBeanDefinitionParser;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	CustomUserDetailsService customUserDetailsService;
	
	// /webjars/** 경로에 대한 요청은 인증/인가 처리하지 않도록 무시합니다.
	@Override
	public void configure(WebSecurity web) throws Exception{
		web.ignoring().antMatchers("/webjars/**");
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception{
		auth.userDetailsService(customUserDetailsService);
	}
	
	// /와 /main에 대한 요청은 누구나 할 수 있지만, 그 외의 요청은 모두 인증 후 접근 가능합니다
	@Override
	protected void configure(HttpSecurity http) throws Exception{
		http.csrf().disable()
			.authorizeRequests()
			.antMatchers("/", "/main","/members/loginerror","/members/joinform","/members/join","/members/welcome").permitAll()
			.antMatchers("/securepage", "/members/**").hasRole("USER")
			.anyRequest().authenticated()
			.and()
				.formLogin()
				.loginPage("/members/loginform")
				.usernameParameter("userId")
				.passwordParameter("password")
				.loginProcessingUrl("/authenticate")
				.failureForwardUrl("/members/loginerror?login_error=1")
				.defaultSuccessUrl("/", true)
				.permitAll()
			.and()
				.logout()
				.logoutUrl("/logout")
				.logoutSuccessUrl("/");
	}
	
	//패스워드 인코더를 빈으로 등록합니다.
	//암호를 인코딩하거나, 인코딩된 암호와 사용자가 입력한 암호가 같은지 확인할 때 사용합니다.
	@Bean
	public PasswordEncoder endcoder() {
		return new BCryptPasswordEncoder();
	}
}

각 부분에 대한 설명

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.userDetailsService(customUserDetailsService);
}

WebSecurityConfigurerAdapter가 갖고 있는 void configure(AuthenticationManagerBuilder auth)를 오버라이딩 하고 있다. 해당 메소드를 오버라이딩 한 후 UserDetailService 인터페이스를 구현하고 있는 객체(customUserDetailService)를 auth.userDetailService() 메소드의 인자로 전달하고 있다.

이렇게 설정된 객체는 아이디/암호를 입력받아 로그인을 처리하는 AuthenticationFilter에서 사용하게 된다.

 

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.csrf().disable()
		.authorizeRequests()
		.antMatchers("/", "/main","/members/loginerror","/members/joinform","/members/join","/members/welcome").permitAll()
		.antMatchers("/securepage", "/members/**").hasRole("USER")
		.anyRequest().authenticated()
		.and()
			.formLogin()
			.loginPage("/members/loginform")
			.usernameParameter("userId")
			.passwordParameter("password")
			.loginProcessingUrl("/authenticate")
			.failureForwardUrl("/members/loginerror?login_error=1")
			.defaultSuccessUrl("/", true)
			.permitAll()
		.and()
			.logout()
			.logoutUrl("/logout")
			.logoutSuccessUrl("/");
}

"/securepage", "/members/**"는 로그인도 되어 있어야 하고 "USER"권한도 가지고 있어야 접근할 수 있도록 설정하고 있다.

 

formLogin()

 

  • loginPage : login form 페이지 URL
  • usernameParameter : form id의 name 속성값
  • passwordParameter : form pw의 name 속성값
  • loginProcessingUrl : form action 값 (security를 이용해 인증처리)
  • failureForwardUrl : 로그인 처리가 실패하게 되면 해당 url로 포워딩
  • defaultSuccessUrl : 로그인 처리가 성공하게 되면 해당 url로 리다이렉트
  • permitAll : 해당 로그인 폼은 누구나 접근 가능

logout()

  • logoutUrl : 로그아웃 처리할 URL (security가 알아서 만들기 때문에, 이 경로로 요청만 하면된다)
  • logoutSuccessUrl : 로그아웃 성공 시 해당 url로 리다이렉트

 

5. 로그인 처리를 위한 컨트롤러와 뷰 작성하기

MemberController.java

import org.edwith.webbe.securityexam.service.MemberService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping(path = "/members")
public class MemberController {
	//스프링 컨테이너가 생성자를 통해 자동으로 주입한다.
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping("/loginform")
	public String loginform() {
		return "members/loginform";
	}
	
	@RequestMapping("/loginerror")
	public String loginerror(@RequestParam("login_error") String loginError) {
		return "members/loginerror";
	}
}

 

 

아이디와 암호를 입력 받는 뷰 작성

members/loginform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
	<div>
		<div>
			<form method="post" action="/securityexam/authenticate">
				<div>
					<label>ID</label>
					<input type="text" name="userId">
				</div>
				<div>
					<label>암호</label>
					<input type="password" name="password">
				</div>
				<div>
					<label></label>
					<input type="submit" value="로그인">
				</div>
			</form>
		</div>
	</div>
</body>
</html>

 

로그인 오류가 발생할 경우 보여줄 화면 작성

members/loginerror.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 오류</title>
</head>
<body>
	<h1>로그인 오류가 발생했습니다. id나 암호를 다시 입력해주세요.</h1>
	<a href="/securityexam/members/loginform">login</a>
</body>
</html>

잘 작동하는지 확인해보자.

1. 서버 실행

2. 로그인 페이지 이동 (http://localhost:8080/securityexam/members/loginform)

3. 로그인

url(http://localhost:8080/securityexam/securepage) 접근 가능

4. 로그아웃 (http://localhost:8080/securityexam/logout)


참고 : 부스트코스 웹 백엔드

https://www.boostcourse.org/

 

다 함께 배우고 성장하는 부스트코스

부스트코스(boostcourse)는 모두 함께 배우고 성장하는 비영리 SW 온라인 플랫폼입니다.

www.boostcourse.org

댓글