일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 관리자페이지
- 인증처리
- 마이페이지
- stock option
- 공통메서드
- 스프링시큐리티
- 회원가입로직
- 이미지 업로드
- 캘린더 라이브러리
- 로그인
- 빌드 및 배포
- 밸류즈 홈페이지
- 배포
- ui탬플릿
- jsonwebtoken
- 달력 라이브러리
- Typesciprt
- Update
- Token
- mypage
- register
- RCPS
- react
- Styled Components
- 밸류즈
- MRC
- userManagement
- Ajax
- 파생상품평가
- 로그인 로직
- Today
- Total
I T H
[스프링프로젝트연습 19] 스프링 프로젝트 구현 - Spring Security 본문
현재까지 작업한 프로젝트에 스프링 시큐리티를 적용하여 로그인 및 회원가입 시 사용자의 비밀번호를 암호화하여 관리하고 로그인 권한에 따라 (일반사용자, 관리자로 구분) 화면 페이지 접속을 구분하여 보고자 한다.
Ex) 일반사용자가 로그인하여 관리자 페이지에 접속 시 권한 없음으로 접속 불가하도록 함
들어가기 전 스프링 시큐리티는 물론 스프링 프레임워크 설정 및 프로그램 개발에 있어 설정하는 방법은 다양하면서도 각기 다른 방식의 설정 방법이 있다. 무엇이 맞고 틀리다는 없으므로 개발자 입장에서 쓰기 편하고 정식 사이트 등에서 가이드 하는 방식으로 작성해서 사용하면 된다.
- 먼저 이번 문서에서는 도메인(빈 클래스)를 사용할 것이다.
- 도메인 클래스의 변수명들은 모두 카멜케이스 형태 (ex> userId 처럼 두개의 단어가 결합되는 경우 뒤의 단어의 첫번째 문자는 대문자 형태)로 작성이 될 것이므로 매퍼 쿼리를 통해 조회된 결과를 도메인 클래스에 담을 때 카멜케이스 형태를 취하도록 리턴 형태에 대한 설정을 해준다.
[ camelCase 설정 ]
kr.co.values.init 패키지의 RootConfig.java 파일에 아래 내용을 추가한다.
/*
* MyBatis + Spring 연동 시, 개발 생산성을 위해 SqlSessionTemplate 클래스를 이용
* SqlSessionTemplate은 선언적 방식의 트랜잭션 제어를 지원 (= AOP 기반 Transaction 제어)
*/
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
// result 컬럼명을 카멜케이스 형태로 변경 (ex> USER_ID -> userId)
// model bean (domain) 클래스에 변수명을 카멜케이스 형태로 설정한 경우 해당 클래스에 데이터를 담기 위해서 사용한다.
sqlSessionFactory().getConfiguration().setMapUnderscoreToCamelCase(true);
SqlSessionTemplate sessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sessionTemplate;
}
스프링 시큐리티를 적용하기 위해 다음과 같이 필요 라이브러리를 설정하여 준다.
[ pom.xml 파일 수정 ]
<!-- spring security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${org.springframework-version}</version>
</dependency>
라이브러리 | 참고 사이트 |
spring-security-web | https://mvnrepository.com/artifact/org.springframework.security/spring-security-web |
spring-security-config | https://mvnrepository.com/artifact/org.springframework.security/spring-security-config |
[ 시큐리티 초기화 클래스 생성 ]
스프링 시큐리티 설정을 위한 초기화 클래스를 상속받은 클래스를 생성한다.
kr.co.values.security 패키지를 생성 후 해당 패키지 아래에 클래스를 생성한다.
package kr.co.values.security;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/* Spring Security를 사용하려면 AbstractSecurityWebApplicationInitializer를
* 상속받는 클래스를 반드시 작성해야 한다. 이 클래스가 있을 경우
* Spring Security가 제공하는 필터들을 사용할 수 있도록 활성화 해준다.
*/
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
[ 시큐리티 설정 클래스 생성 ]
로그인 페이지, 로그인 실패, 성공, 접속 페이지에 대한 권한 설정 등의 시큐리티 설정을 위한 클래스이다.
여기서는 앞서 만들었던 admin 페이지에 대한 접속 권한을 관리자만 부여하여 관리자로 로그인한 경우에만 접속이 가능하도록 처리하기 위한 설정을 진행한다.
kr.co.values.security 패키지 아래에 WebSecurityConfigurerAdapter 클래스를 상속 받은 클래스를 생성하고 아래 내용을 입력한다.
Configure 메소드 (파라미터가 다른 동일한 이름의 메소드가 있으니 주의)를 통해 특정 url 패턴에 대해 권한 부여가 가능하다.
package kr.co.values.security;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/* Spring Security를 이용해 로그인/로그아웃/인증/인가 등을 처리하기 위한 설정 파일이다.
* @EnableWebSecurity가 붙어 있을 경우 Spring Security를 구성하는 기본적인 Bean들을 자동으로 구성해준다.
* WebSecurityConfigurerAdapter를 상속받으면 특정 메소드를 오버라이딩 함으로써 좀 더 손쉽게 설정할 수 있다.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
* 주의) configure() 메소드는 파라미터 타입이 다른 동일한 메소드명이 존재하므로 확인 필수
* 인증 및 인가가 필요없는 url 을 지정하여 시큐리티 미적용되도록 처리한다.
* ex > /resources /css /img 등등
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**");
}
/* configure(HttpSecurity http) 메소드를 오버라이딩 한다는 것은 인증/인가에 대한 설정을 한다는 의미이다.
* 가장 중요한 메소드로 볼 수 있다.
*
* http.csrf().disable()는 csrf() 기능을 끄라는 설정이다.
* csrf는 보안 설정 중 post방식으로 값을 전송할 때 token을 사용해야하는 보안 설정이다.
* csrf은 기본으로 설정되어 있는데 사용시 보안성은 높아지지만
* 개발초기에는 불편함이 있다는 단점이 있어서 기능을 끈 것이다.
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") 이렇게도 사용 가능하다.
.anyRequest().permitAll(); // permitAll() : 이 외 모든 url 접근 허용 / authenticated() : 이 외 모든 url 접근은 로그인 후 사용 가능하도록 설정
// login 설정
http
.formLogin()
.loginPage("/login") // GET 요청 (login form을 보여줌)
.loginProcessingUrl("/login") // POST 요청 (login 창에 입력한 데이터를 처리)
.usernameParameter("userId") // login에 필요한 id 값을 userId로 설정 (default는 username)
.passwordParameter("password") // login에 필요한 password 값을 password(default)로 설정
.defaultSuccessUrl("/"); // login에 성공하면 /로 redirect
}
/*
* 비밀번호 암호화를 위해 사용한다.
*/
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
- 시큐리티 설정 클래스를 생성 후 프로젝트에 해당 클래스가 설정용 클래스임을 알려주기 위해 WebConfig.java 파일을 수정하여 준다.
/**
* root-context.xml 파일을 대신하는 클래스를 지정하는 메소드
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class, SecurityConfig.class};
}
다음으로 사용자 정보를 담기 위한 모델빈 (도메인) 클래스를 하나 생성한다.
[ 모델빈클래스 생성 – User.java 파일 생성 ]
kr.co.values.domain 패키지를 생성하고 아래와 같이 클래스를 생성한다.
package kr.co.values.domain;
/*
* 사용자 테이블 컬럼 구조
* `USER_ID` varchar(20) NOT NULL,
`USER_PWD` varchar(100) NOT NULL,
`USER_NAME` varchar(100) DEFAULT NULL,
`USER_AUTH` char(1) DEFAULT NULL,
`USER_TEL` varchar(10) DEFAULT NULL,
`USER_EMAIL` varchar(50) DEFAULT NULL,
`INPUT_DATETIME` datetime DEFAULT NULL
*/
public class User {
private String userId;
private String userPwd;
private String userName;
private String userAuth;
private String userTel;
private String userEmail;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAuth() {
return userAuth;
}
public void setUserAuth(String userAuth) {
this.userAuth = userAuth;
}
public String getUserTel() {
return userTel;
}
public void setUserTel(String userTel) {
this.userTel = userTel;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
}
도메인 클래스의 경우 모두 타이핑 할 필요없이 아래와 같이 변수만 설정해두고
우측 마우스 > source > getter and setter 메뉴를 통해 메소드를 자동 완성할 수 있다.
[ 매퍼 인터페이스 설정 ]
패키지 아래에 사용자 정보를 조회하기 위한 매퍼 인터페이스를 생성하고 아래와 같이 작성 및 쿼리 매핑을 처리한다.
- UserMapper.java
package kr.co.values.persistence;
import java.util.Map;
import kr.co.values.domain.User;
public interface UserMapper {
// 사용자 정보 가져오기
User getUserById(Map<String, Object> map);
}
- UserMapper.xml
- USER_AUTH 가 1이면 일반사용자 / 2이면 관리자로 설정하기 위해 CASE 구문을 사용하였다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.values.persistence.UserMapper">
<!-- 회원 정보 가져오기 -->
<select id="getUserById" parameterType="hashmap" resultType="kr.co.values.domain.User">
SELECT USER_ID
, USER_PWD
, USER_NAME
, CASE WHEN USER_AUTH = '1' THEN 'USER' ELSE 'ADMIN' END AS USER_AUTH
, USER_TEL
, USER_EMAIL
FROM TBL_USER_INFO
WHERE USER_ID = #{userId}
</select>
</mapper>
[ 서비스 구현 – UserService.java ]
패키지 아래에 서비스 클래스를 생성하고 아래와 같이 입력한다.
BCryptPasswordEncoder 는 기본적으로 시큐리티에서 사용되는 암호화 클래스로 비밀번호를 암호화 하여 데이터베이스에 저장하기 위해 사용하며, 암호화된 비밀번호와 사용자가 로그인 페이지에서 입력한 원문 비밀번호가 일치하는지 체크하기 위해 사용된다.
package kr.co.values.service;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import kr.co.values.domain.User;
import kr.co.values.persistence.UserMapper;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public User getUserById(Map<String, Object> map) {
return userMapper.getUserById(map);
}
public PasswordEncoder passwordEncoder() {
return this.passwordEncoder;
}
}
[ 인증 클래스 생성 ]
시큐리티의 AuthenticationProvider 인터페이스를 구현 받은 클래스를 생성한다.
해당 클래스는 로그인 시도 후 인증에 대한 처리를 담당하는 클래스로
사용자가 입력한 비밀번호가 일치하는지 체크하고 로그인 정보가 정확하다면 권한을 부여 후 인증 토큰을 발행하여 주는 역할을 한다.
package kr.co.values.security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import kr.co.values.domain.User;
import kr.co.values.service.UserService;
@Component
public class AuthProvider implements AuthenticationProvider {
@Autowired
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String id = (String) authentication.getPrincipal(); // 로그인 창에 입력한 id
String password = (String) authentication.getCredentials(); // 로그인 창에 입력한 password
System.out.println("id : " + id);
System.out.println("password : " + password);
PasswordEncoder passwordEncoder = userService.passwordEncoder();
UsernamePasswordAuthenticationToken token;
Map<String, Object> map = new HashMap<String, Object>();
map.put("userId", id);
User user = userService.getUserById(map);
System.out.println("=================================================");
System.out.println(user);
System.out.println("user.getUserPwd() : " + user.getUserPwd());
System.out.println("passwordEncoder.encode(password) : " + passwordEncoder.encode(password));
System.out.println(passwordEncoder.matches(password, user.getUserPwd()));
System.out.println("user auth : " + user.getUserAuth());
if (user != null && passwordEncoder.matches(password, user.getUserPwd())) { // 일치하는 user 정보가 있는지 확인
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority("ROLE_" + user.getUserAuth())); // 권한 부여
token = new UsernamePasswordAuthenticationToken(user.getUserId(), null, roles);
// 인증된 user 정보를 담아 SecurityContextHolder에 저장되는 token
return token;
}
throw new BadCredentialsException("No such user or wrong password.");
// Exception을 던지지 않고 다른 값을 반환하면 authenticate() 메서드는 정상적으로 실행된 것이므로 인증되지 않았다면 Exception을 throw 해야 한다.
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
만약 비밀번호를 잊어버렸거나 데이터베이스 테이블에 사용자 테이블 컬럼 중 비밀번호 컬럼이 원문 그대로 저장되어 있다면, 사용자 최초 가입 시에는 인코딩 된 비밀번호가 등록될 것이므로 이번에는 수동으로 비밀번호를 업데이트한 후 사용하는 것으로 한다.
- 수동으로 업데이트할 인코딩 된 비밀번호는 특정 컨트롤러나 테스트 클래스를 통해 passwordEncoder.encode(“원문비밀번호”) 메소드 호출을 통해 변환된 스트링 값을 받아서 사용할 수 있다.
@Test
public void testM() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("kkk123");
System.out.println("password : " + password);
}
여기까지 설정이 끝나면 시큐리티 사용을 위한 기본 설정이 끝이 났다.
다음으로 로그인을 위한 로그인 컨트롤러 및 로그인 페이지를 구현하여 준다.
[ 로그인 컨트롤러 구현 – LoginController.java ]
kr.co.values.web 패키지 아래에 컨트롤러 클래스를 생성하고 아래와 같이 입력한다.
/login 경로로 접속한 경우에 현재 로그인 된 정보가 없는 경우에는 로그인 페이지 (cLogin.jsp) 로 이동하도록 처리
로그인 된 정보가 있는 경우에는 /main/home.do 페이지로 이동하도록 처리한다.
package kr.co.values.web;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage() { // 로그인되지 않은 상태이면 로그인 페이지를, 로그인된 상태이면 home 페이지를 보여줌
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof AnonymousAuthenticationToken)
return "cLogin";
return "redirect:/main/home.do";
}
}
[ 로그인 페이지 구현 ]
src\main\webapp\WEB-INF\views 폴더 아래에 cLogin.jsp 파일을 생성 후 아래와 같이 입력한다. 위에서 시큐리티 설정 시 사용된 usernameParameter / passwordParameter 의 값과 일치하는 name 속성의 input 태그들로 설정을 맞춰주어야 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<h2><c:out value="${error}" /></h2>
<h2><c:out value="${logout}" /></h2>
<form method="post" action="/login">
<label>아이디</label>
<br/>
<input type="text" name="userId">
<br/>
<label>비밀번호</label>
<br/>
<input type="password" name="password">
<input type="submit">
<input type="hidden" name="${_csrf.paramterName}" value="${_csrf.token}" />
</form>
</body>
</html>
[ 로그인 테스트 ]
WAS 재 실행 후
http://localhost:8080/login 페이지로 접속한다.
Case 1.
- 일반 사용자로 로그인 후 로그인 확인
- 로그인 된 상태에서 http://localhost:8080/admin/main.do 페이지로 접속을 시도
- 관리자용 페이지이므로 일반 사용자는 접속이 불가
Case 2.
- 관리자로 로그인 후 로그인 확인
- 로그인 된 상태에서 http://localhost:8080/admin/main.do 페이지로 접속을 시도
- 관리자용 페이지 접근이 가능한지 확인
[ 결과화면 ]
Case1 - 일반 사용자로 접속시 - admin페이지 접근금지

Case2 - 관리자 권한이 있는 아이디로 접속시 - admin페이지 접근허가
'Spring Basic' 카테고리의 다른 글
[Spring boot] 2. Spring Boot 설정 (Week 1) (0) | 2024.01.24 |
---|---|
[Spring Boot] 1. 계획 수립 / Spring Boot 시작하기 (Week 1) (0) | 2024.01.24 |
[스프링프로젝트연습 18] 스프링 프로젝트 구현 - 관리자페이지: 이미지 업로드 (0) | 2024.01.22 |
[스프링프로젝트연습 17] 스프링 프로젝트 구현 - Junit 테스트 (0) | 2024.01.22 |
[스프링프로젝트연습 16] 스프링 프로젝트 구현 - CRUD 서비스 로직 추가 (0) | 2024.01.22 |