본문 바로가기

Springboot

Springboot security (3) - 권한설정

이번에는 화면단을 thymeleaf에서 jsp로 변경하고

로그인 / 가입 / 권한에 따른 페이지 처리를 적용하는 과정에 대한 부분입니다.

이전 포스팅에서 바뀐부분만 설명하겠습니다.

 

기본적인 jsp구조와 security정보는 https://victorydntmd.tistory.com/328 이사이트를 참고했습니다.

 

1. Application structure

2. pom.xml

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-taglibs</artifactId>
</dependency>

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
</dependency>

<dependency>
  <groupId>jstl</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

기존의 thymeleaf를빼고 jsp를 이용하기 위해 tomcat-embed-jasper를 추가하였습니다.

taglibs의 경우 jsp에서 권한 및 로그인여부 파악에 사용됩니다.

 

3. properties 추가

spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

 

4. index.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>메인</title>
</head>
<body>
	<h1>메인페이지</h1>
    <hr>
    <sec:authorize access="isAnonymous()"><a href="/user/loginPage">로그인</a></sec:authorize>
    <sec:authorize access="isAuthenticated()"><a href="/user/logout">로그아웃</a></sec:authorize>
    <sec:authorize access="isAnonymous()"><a href="/user/signup">회원가입</a></sec:authorize>
    <sec:authorize access="hasAuthority('ADMIN') or hasAuthority('USER')"><a href="/user/info">내정보</a></sec:authorize><br>
    <sec:authorize access="hasAuthority('USER')"><a href="/user/admin">admin</a></sec:authorize><br>
</body>
</html>

참고 : https://kimsaemjava.tistory.com/250

5. login.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
    <h1>로그인</h1>
    <hr>

	<form action="/login" method="post">
        <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>
        <input type="text" name="username" placeholder="이메일 입력해주세요">
        <input type="password" name="password" placeholder="비밀번호">
        <button type="submit">로그인</button>
    </form>    
</body>
</html>

security를 사용할경우 csrf토큰을 같이 보내줘야 제대로 동작하게 됩니다.

하지만 주석처리를 한 이유는 이후에 나오는 security 설정에서 .csrf().disable(); 를 처리해주었기 때문입니다.

 

6. loginSuccess.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>

<!DOCTYPE html>
<html lang="en" xmlns:sec="" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>로그인 성공</title>
</head>
<body>
	<h1>로그인 성공!!</h1>
	<hr>
	<p>
		<sec:authorize access="isAuthenticated()">
			<sec:authentication property="principal" var="user" />
			<div>${user.name}님</div>
			<div>${user.authorities}</div>
		</sec:authorize>
	</p>
    <sec:authorize access="hasAuthority('ADMIN') or hasAuthority('USER')"><a href="/user/info">내정보</a></sec:authorize><br>
    <sec:authorize access="hasAuthority('USER')"><a href="/user/admin">admin</a></sec:authorize><br>
	
	<a href="/">메인으로 이동</a>
</body>
</html>

principal에 로그인한 유저 정보가 담겨있습니다.

hasAuthority를 이용하여 ADMIN권한 또는 USER권한일경우 내정보에 대한 메뉴를 화면에 출력하고

hasAuthority를 이용하여 ADMIN권한일경우 admin메뉴를 화면에 출력합니다.

 

7. logout.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>로그아웃</title>
</head>
<body>
    <h1>로그아웃 처리되었습니다.</h1>
    <hr>
    <a href="/">메인으로 이동</a>
</body>
</html>

 

8. signup.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
    <h1>회원 가입</h1>
    <hr>
	<form action="/user/signup" method="post">
        <input type="text" name="username" placeholder="이메일 입력해주세요">
        <input type="password" name="password" placeholder="비밀번호">
        <input type="text" name="name" placeholder="이름">
        <input type="hidden" name="isAccountNonExpired" value="true">
        <input type="hidden" name="isAccountNonLocked" value="true">
        <input type="hidden" name="isCredentialsNonExpired" value="true">
        <input type="hidden" name="isEnabled" value="true">
        <button type="submit">가입하기</button>
    </form>
</body>
</html>

참고 : https://zgundam.tistory.com/49

9. myinfo.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>내정보</title>
</head>
<body>
    <h1>내정보 확인 페이지입니다.</h1>
    <hr>
</body>
</html>

 

10. admin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>어드민</title>
</head>
<body>
    <h1>어드민 페이지입니다.</h1>
    <hr>
</body>
</html>

 

11. denied.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>접근 거부</title>
</head>
<body>
    <h1>접근 불가 페이지입니다.</h1>
    <hr>
</body>
</html>

 

12. SecurityConfig 수정

import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.security_blog.yg1110.servicer.UserService;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	UserService userService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/user/admin/**").access("hasAuthority('ADMIN')")
			.antMatchers("/user/myinfo").access("hasAuthority('USER')") // 페이지 권한 설정
			.antMatchers("/", "/user/signup", "/user/denied", "/user/logout/result").permitAll()
			.anyRequest().authenticated()
			.and()
			.formLogin().loginPage("/user/loginPage")
			.loginProcessingUrl("/login")
			.defaultSuccessUrl("/user/login/result")
			.permitAll() // 로그인 설정
			.and()
			.logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // 로그아웃 설정
			.logoutSuccessUrl("/user/logout/result").invalidateHttpSession(true)
			.and()
			.exceptionHandling().accessDeniedPage("/user/denied") // 403 예외처리 핸들링
			.and()
			.csrf().disable();
	}

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

 

 

.antMathchers("/user/admin/**").access("hasAuthority('ADMIN')") 해당 경로는 ADMIN권한만 연결이 가능합니다.
.antMathchers("/user/myinfo/**").access("hasAuthority('USER')") 해당 경로는 USER권한만 연결이가능합니다.
.antMatchers("/", "/user/signup", "/user/denied", "/user/logout/result").permitAll() 해당 경로는 모두 연결이 가능합니다.
.anyRequest().authenticated() 나머지 경로는 권한을부여받아야지만(로그인) 연결이 가능합니다.
.formLogin().loginPage("/user/loginPage") 해당 경로의 커스텀된 로그인 페이지를 출력합니다.
.loginProcessingUrl("/login") 로그인버튼을 클릭했을시 action의 경로입니다.(기본적으로 post)
.defaultSuccessUrl("/user/login/result") 로그인이 성공했을경우 해당경로의 화면을 출력합니다.
.logout().logoutRequstMatcher(new AntPathRequestMatcher("/user/logout")) 해당경로로 요청이 왔을경우 로그아웃이 진행됩니다.
.logoutSuccessUrl("/user/logout/result").invalidateHttpSession(true) 로그아웃 성공시 출력되는 경로입니다.
.exceptionHandling().accessDeniedPage("/user/denied") 권한이 없을 경우 해당 경로의 페이지가 출력됩니다.
.csrf().disable() 요청을 받을때 csrf토큰이 없어도 호출됩니다.

 

13. Usercontroller 수정

package com.security_blog.yg1110.controller;

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.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.security_blog.yg1110.domain.User;
import com.security_blog.yg1110.mapper.UserMapper;
import com.security_blog.yg1110.servicer.UserServiceImpl;

@Controller
public class UserController {
	@Autowired UserMapper userMapper;

	@Autowired UserServiceImpl userservice;

    // 메인 페이지
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String index() {
        return "index";
    }

    // 회원가입 페이지
    @GetMapping("/user/signup")
    public String dispSignup() {
        return "signup";
    }

    // 회원가입 처리
    @PostMapping("/user/signup")
    public String execSignup(User user) {
    	System.out.println(user);
    	List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
//		authorities.add(new SimpleGrantedAuthority("ADMIN"));
		authorities.add(new SimpleGrantedAuthority("USER"));
		user.setAuthorities(authorities);
    	userservice.createUser(user);
        return "redirect:/user/login";
    }

    // 로그인 페이지
    @GetMapping("/user/loginPage")
    public String dispLogin() {
        return "login";
    }

    // 로그인 결과 페이지
    @GetMapping("/user/login/result")
    public String dispLoginResult() {
        return "loginSuccess";
    }

    // 로그아웃 결과 페이지
    @GetMapping("/user/logout/result")
    public String dispLogout() {
        return "logout";
    }

    // 접근 거부 페이지
    @GetMapping("/user/denied")
    public String dispDenied() {
        return "denied";
    }

    // 내 정보 페이지
    @GetMapping("/user/info")
    public String dispMyInfo() {
        return "myinfo";
    }

    // 어드민 페이지
    @GetMapping("/user/admin")
    public String dispAdmin() {
        return "admin";
    }
}

회원가입시 무조건 USER권한을 따로 리스트에 넣어서 주고 있는데 이부분에 대해서 추후에 수정예정입니다.

 

 

참고사이트 : https://victorydntmd.tistory.com/328