본문 바로가기

프로그래밍/스프링(spring)

[스프링/spring] 스프링 시큐리티적용 ( Spring-security)

오늘은 저번에 DB 사용하지 않고 구현 해봤는데, 이번에는 jdbc 를 이용해 사용해 보도록 하겠습니다.

처음에는 pom.xml에 몇가지 라이브러리를 추가하겠습니다.

 

 

을 추가하고

룸복은 라이브러리를 받기만 해서 되는게 아니라

https://projectlombok.org/download

 

Download

 

projectlombok.org

룸복 정식 홈페이지에서 다운을 받아 실행시켜 줘야 합니다.

 

 

java -jar lombook.jar

 

 

[Specify location] 버튼을 눌러 Eclipse에 있는 eclipse.ini를 open 시켜줍니다.

 

 

 

[Quit installer] 를 눌러 룸복을 설치합니다.

 

 

 

저번에 만든 security-context.xml에 변화를 주도록 하겠습니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="AccessDenied" class="com.spring.example.security.LoginSuccessHandler"></bean>
	<bean id="DeniedHandler" class="com.spring.example.security.DeniedHandler"></bean>
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
	<bean id="customUserDetails" class="com.spring.example.security.CustomUserDetailsService" />
	
	<security:http>
		
		<security:intercept-url pattern="/security/all" access="permitAll" />
		
		<security:intercept-url pattern="/security/member" access="hasRole('ROLE_MEMBER')" />
		
		<security:access-denied-handler ref="DeniedHandler" />
		
		<security:form-login login-page="/login" 
			authentication-success-handler-ref="AccessDenied" />
		
		
		
	</security:http>
	
	<security:authentication-manager>
		<security:authentication-provider user-service-ref="customUserDetails">
			
			<security:password-encoder ref="bcryptPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

 

소스는 사진에 있는거랑 같은 소스입니다.

이 다음 security-context.xml 위 쪽에 <bean id = ""> 로 되어있는 걸 만들어 주도록 하겠습니다.

 

 

 

 

CustomUser.java 는

 

 

package com.spring.example.security;

import java.util.Collection;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import com.spring.example.VO.MemberVO;

import lombok.Getter;

@Getter
public class CustomUser extends User{

	private static final long serialVersionUID = 1L;
	
	private MemberVO memberVO;
	
	public CustomUser(String id, String password,
			Collection<? extends GrantedAuthority> auth) {
		
		super(id, password, auth);
	}
	
	public CustomUser(MemberVO vo) {
		super(vo.getId(), vo.getPassword(), vo.getAuthList().stream()
				.map(auth -> new SimpleGrantedAuthority(auth.getAuth()))
				.collect(Collectors.toList()));
		
		this.memberVO = vo;
	}
	
}

 

이렇게 써주시고요.

CustomUserDetailsService.java는

 

package com.spring.example.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.spring.example.VO.MemberVO;
import com.spring.example.dao.MemberDao;

import lombok.Setter;

public class CustomUserDetailsService implements UserDetailsService{

	@Setter(onMethod_ = { @Autowired })
	private MemberDao memberDao;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		MemberVO vo = new MemberVO();
		
		try {
			vo = memberDao.Login(username);
			
		} catch(Exception e) {
			e.printStackTrace();
		}
		
		return vo == null ? null : new CustomUser(vo);
	}

}

 

DeninedHandler.class 예외 발생이 생겼을떄의 코드인데요 이거는

 

package com.spring.example.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.*;

public class DeniedHandler implements AccessDeniedHandler{

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		
		System.out.println("에러 났습니다.");
		
		response.sendRedirect("/security/accessError");
		
	}
	
}

 

LoginSuccessHandler.java는 로그인이 성공 되었을 떄 권한에 따라 화면이 움직는 역활을 하는데 이 소스는 밑에와 같습니다.

package com.spring.example.security;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class LoginSuccessHandler implements AuthenticationSuccessHandler{

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		
		List<String> rolenames = new ArrayList<String>();
		
		System.out.println("로그인 성공");
		
		authentication.getAuthorities().forEach(authority -> {
			rolenames.add(authority.getAuthority());
		});
		
		if(rolenames.contains("ROLE_ADMIN")) {
			response.sendRedirect("/security/admin");
			return;
		}
		
		if(rolenames.contains("ROLE_MEMBER")) {
			response.sendRedirect("/security/member");
			return;
		}
		
		response.sendRedirect("/");
		
	}
	
}

 

여기서 저희가 만들어둔 MemberVO 와 AuthVO를 수정하고 만들어야 제대로 진행이 될것같습니다.

 

 

AuthVO.java 는

 

package com.spring.example.VO;

public class AuthVO {
	
	private String userid;
	private String auth;
	
	public String getUserid() {
		return userid;
	}
	public void setUserid(String userid) {
		this.userid = userid;
	}
	public String getAuth() {
		return auth;
	}
	public void setAuth(String auth) {
		this.auth = auth;
	}
	
	
}

 

로 만들어주시고요.

MemberVO는 이렇게 수정해주세요.

 

package com.spring.example.VO;

import java.util.Date;
import java.util.List;

public class MemberVO {
	
	private String id;
	private String password;
	private String userName;
	private boolean enabled;
	
	private Date regdate;
	private List<AuthVO> authList;
	
	// getter, setter
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public boolean isEnabled() {
		return enabled;
	}
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}
	public Date getRegdate() {
		return regdate;
	}
	public void setRegdate(Date regdate) {
		this.regdate = regdate;
	}
	public List<AuthVO> getAuthList() {
		return authList;
	}
	public void setAuthList(List<AuthVO> authList) {
		this.authList = authList;
	}
	
	// 생성자
	public MemberVO() {
		
	}
	
	// to String
	@Override
	public String toString() {
		return "MemberVO [id=" + id + ", password=" + password + "]";
	}
}

 

위 쪽에 에러가 났을때 가는 컨트롤러를 지정해주겠습니다.

 

package com.spring.example.Controller;

import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/security/*")
@Controller
public class SecurityController {
	
	@GetMapping("/all")
	public String all() {
		return "home";
	}
	
	@GetMapping("/member")
	public String member() {
		return "security/member";
	}
	
	@GetMapping("/admin")
	public String admin() {
		return "security/admin";
	}
	
	@GetMapping("/accessError")
	public String error(AuthenticationSuccessHandler auth, Model model) {
		
		model.addAttribute("msg", "Access Demoed");
		
		return "common/LoginPage";
		
	}
	
}

 

로 지정해주시고요.

에러가 날 시 LoginPage로 이동하게 해주세요.

그리고 저번 시간에 만든 로그인 체크 해주는 Service, ServiceImple, dao 를 수정해줘야 합니다. 기존에는 vo를 넣어 id와 Password를 체크해줬지만 시큐리티에서는 id 를 체크하고 password를 쿼리를 가져오는 역활을 해줘 쿼리에 id 만 필요하기 때문에 수정하도록 하겠습니다.

 

 

package com.spring.example.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.spring.example.VO.MemberVO;

@Repository
public class MemberDao {
	@Autowired
	private SqlSessionTemplate mybatis;
	
	public MemberVO Login(String userid) throws Exception {
		return mybatis.selectOne("Member.Login", userid);
	}
	
}
package com.spring.example.service;

import com.spring.example.VO.MemberVO;

public interface MemberService {
	
	public MemberVO Login(String userid) throws Exception;
}
package com.spring.example.serviceImpl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.example.VO.MemberVO;
import com.spring.example.dao.MemberDao;
import com.spring.example.service.MemberService;

@Service
public class MemberServiceImpl implements MemberService{

	@Autowired
	private MemberDao memberDao;
	
	@Override
	public MemberVO Login(String userid) throws Exception {
		return memberDao.Login(userid);
	}
	
}

 

이렇게 매개변수는 String으로 변경하고 return 타입은 vo 가 되도록 변경해줍니다.

쿼리문도 바꿔야 하기 때문에 이렇게 변경해주시고요.

 

 

<?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="Member">

	<resultMap type="MemberVO" id="memberMap">
		<id property="id" column="userid" />
		<result property="id" column="userid" />
		<result property="password" column="userpw" />
		<result property="userName" column="username" />
		<result property="regdate" column="regdate" />
		<collection property="authList" resultMap="authMap">
		</collection>
	</resultMap>
	
	<resultMap type="AuthVO" id="authMap">
		<result property="userid" column="userid" />
		<result property="auth" column="auth" />
	</resultMap>
	
 
    <select id="Login"  parameterType="String" resultMap="memberMap">
        SELECT a.userid userid
             , a.userpw userpw
             , a.username username
             , a.regdate regdate
             , a.enabled
             , b.auth as auth
         from users a 
         	LEFT JOIN authorities b 
         	  ON a.userid= b.userid
        where a.userid = #{userid}
    </select>
    
</mapper>

 

AuthVO도 mybatis 가 잘 찾을 수 있도록 설정을 해줘야합니다.

 

 

mybatis-config.xml 를 변경해주세요.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
	<typeAliases>
  		<typeAlias alias="MemberVO" type="com.spring.example.VO.MemberVO" />
  		<typeAlias alias="AuthVO" type="com.spring.example.VO.AuthVO" />
	</typeAliases>
</configuration>

 

변경된 쿼리 처럼 Table를 추가해주어야 합니다.

 

 

create table users(
userid varchar(50) not null primary key,
userpw varchar(100) not null,
username varchar(100) not null,
regdate TIMESTAMP default CURRENT_TIMESTAMP,
enabled char(1) default '1'
);

create table authorities (
userid varchar(50) not null,
auth varchar(50) not null
);

insert into authorities values ( 'member', 'ROLE_MEMBER');

 

그리고 jUnit을 통해 users 테이블에 인코딩된 패스워드를 넣어 보도록 하겠습니다.

( 꼭 test 폴더에 넣어서 실행해야 합니다. )

 

 

package com.spring.example.security;

import java.sql.Connection;
import java.sql.PreparedStatement;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
	"file:src/main/webapp/WEB-INF/spring/root-context.xml",
	"file:src/main/webapp/WEB-INF/spring/security-context.xml"
})
public class MemberTest {

	@Setter(onMethod_ = @Autowired)
	private PasswordEncoder pwencoder;
	
	@Setter(onMethod_ = @Autowired)
	private DataSource ds;
	
	@Test
	public void testInserMember() {
		String sql = "insert into users(userid, userpw, username) values(?, ?, ?)";
	
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			
			con = ds.getConnection();
			pstmt = con.prepareStatement(sql);
			
			pstmt.setString(1, "member");
			pstmt.setString(2, pwencoder.encode("member"));
			pstmt.setString(3, "member");
			
			pstmt.executeUpdate();
			
		} catch(Exception e) {
			e.printStackTrace();
			System.out.println("테스트");
		}
		
	}
	
	
}

 

를 실행 하면 쿼리문에 member가 들어가게 됩니다.

실행 방법은

 

 

를 클릭하면

 

 

 

JUnit으로 실행 해 줍니다.

쿼리를 실행해보면

 

 

 

uses 테이블에 이상하게 데이터가 들어가면 성공하게 된것입니다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
<style>
   form{
       text-align: center;
       margin:0 auto;
   }
   .border{
       margin:0 auto;
       width:400px;
       height:500px;
       border:1px solid #000;
       border-radius: 10%;
   }
   img{
       margin-top:20px;
       margin-bottom:80px;
   }
   input{
       width:300px;
   }
</style>
</head>
<body>
    <form action ="/login" method="POST">
        <div class="border">
            <h1>로그인</h1>
            <img src ="/images/profle.jpg"><br>
            <c:if test="${empty msg ? true : false}" var="result">
            	<h2><c:out value="${msg}" /></h2>
            </c:if>
            <input type="text"  name="username" id="username" placeholder="아이디를 입력해주세요."><br>
            <input type="password" name="password" id="password" placeholder="비밀번호를 입력해주세요."><br><br>
            <input type="submit" value="로그인">
            
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </div> 
    </form>
</body>
</html>

 

 

로그인도 csrf 속성 때문에

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

를 추가하였습니다.

자, 이제 서버를 다시 실행 시켜 로그인 페이지로 가서

 

아이디는 member 비밀번호도 member로 입력하게 되면

 

 

ROLE_MEMBER 권한 은 security/member로 가게 LoginSuccessHendler.java 에 설정했기 때문에 member 페이지로 가게 되었습니다.