SPRING - SECURITY
- SecurityConfig -> 화면 -> ServiceImpl 성공하면 세션에 저장 -> Controller
홈화면(로그인 전에)
127.0.0.1:9090/ROOT/security_home
127.0.0.1:9090/ROOT/member/security_join
127.0.0.1:9090/ROOT/member/security_login
관리자 홈화면(로그인 후에)
127.0.0.1:9090/ROOT/security_admin/home
127.0.0.1:9090/ROOT/security_admin/insert
판매자 홈화면(로그인 후에)
127.0.0.1:9090/ROOT/security_seller/home
127.0.0.1:9090/ROOT/security_seller/item
고객 홈화면(로그인 후에)
127.0.0.1:9090/ROOT/security_customer/home
127.0.0.1:9090/ROOT/security_customer/mypage
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
extends
= 상속을 받음, 특정기능을 오버라이드 해서 사용하면 됨protected void configure(HttpSecurity http) throws Exception
extends
한 WebSecurityConfigurerAdapter
가 부모 클래스고,configure(HttpSecurity http)
메소드를 호출해서super
= 부모의 기능을 그대로 다 사용한다는 뜻http.csrf().disable();
http.headers().frameOptions().sameOrigin();
package com.example.config;
import com.example.handler.MyLoginSuccessHandler;
import com.example.handler.MyLogoutSuccessHandler;
import com.example.service.MemberDetailServiceImpl;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 암호를 여기서 해시하고 확인하는 이유는
// ServiceImpl에서 User로 리턴 받은 이후에
// 암호를 비교해서 로그인 처리를 하기때문에
// 그 이전에 ServiceImpl에서 내가 궂이 비교 할 필요가 없다
// 1. 직접 만든 DetailServiceImpl 객체 생성
@Autowired
MemberDetailServiceImpl mService;
// 2. 암호화 방법 객체생성, @Bean은 서버구동시 자동으로 객체생성
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// 3. 직접만든 DetailServiceImpl에 암호화방법 적용
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(mService).passwordEncoder(bCryptPasswordEncoder());
}
// 시큐리티 라이브러리 설치 후 모든 페이지 접근권한 막힘
// 로그인 페이지로 가는데 내가만든 페이지가 아니고 자체 로그인페이지가 뜸
// 여기서 설정을 해야함
// extends = 상속을 받음, 특정기능을 오버라이드 해서 사용하면 됨
@Override
protected void configure(HttpSecurity http) throws Exception {
// WebSecurityConfigurerAdapter가 부모 클래스고,
// configure(HttpSecurity http) 메소드를 호출해서
// super = 부모의 기능을 그대로 다 사용한다는 뜻
// 때문에 super를 사용하지 않는다 = 기존기능을 다 빼버린다 는 뜻
// super.configure(http);
// 각 주소마다의(페이지 별) 접근 권한 부여
http.authorizeRequests()
.antMatchers("/security_admin", "/security_admin/**")
.hasAnyAuthority("ADMIN")
.antMatchers("/security_seller", "/security_seller/**")
.hasAnyAuthority("ADMIN", "SELLER")
.antMatchers("/security_customer", "/security_customer/**")
.hasAnyAuthority("CUSTOMER")
.anyRequest().permitAll(); // 나머지요청은 모두 허용
// 로그인페이지 설정, 단 POST는 직접 만들지 않음
// 로그인 화면 html에서 설정한 name값을 사용(uemail, upw)
// 로그인 정보 입력 한 데이터는 바로 서비스로 감
http.formLogin()
.loginPage("/member/security_login")
.loginProcessingUrl("/member/security_loginaction") // th:action 명
.usernameParameter("uemail") // 아이디
.passwordParameter("upw") // 패스워드
.successHandler(new MyLoginSuccessHandler()) // 성공했을때 명령을 수행
// .defaultSuccessUrl("/security_home", true) // 성공했을때 가는 페이지
.permitAll();
// 로그아웃페이지 설정
// url에 맞게 POST로 호출하면 됨
http.logout()
.logoutUrl("/member/security_logout") // 로그아웃 명령을 받을 주소
.logoutSuccessHandler(new MyLogoutSuccessHandler()) // 성공했을때 명령을 수행
// .logoutSuccessUrl("/security_home") // 로그아웃했을때 이동하는 페이지
.invalidateHttpSession(true) // 세션 초기화
.clearAuthentication(true) // 권한초기화
.permitAll();
// 접근권한 불가 페이지
http.exceptionHandling().accessDeniedPage("/security_403");
// h2-console DB 사용을 위해서 임시로
// 나중에 빼야함
// http.csrf().disable(); // 보안에 취약
// http.headers().frameOptions().sameOrigin();
http.csrf()
.ignoringAntMatchers("/h2-console/**");
http.headers().frameOptions().sameOrigin();
}
}
UserDetailsService
사용 할꺼다loadUserByUsername(String username)
UserDetails
User(아이디, 암호, 권한)
return user;
package com.example.service;
import java.util.Collection;
import com.example.dto.MemberDTO;
import com.example.dto.MyUserDTO;
import com.example.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 MemberDetailServiceImpl implements UserDetailsService {
// Mapper 사용하겠다 선언
@Autowired
MemberMapper mMapper;
// UserDetailsService에 설계 된 것을 implments 해서 오버라이드 후 사용
// 로그인에서 입력하는 정보중에서 아이디를 받음
// MemberMapper를 이용해서 정보를 가져와서 UserDetails로 리턴
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("=== MemberDetailServiceImpl/username => " + username);
MemberDTO member = mMapper.memberEmail(username);
// 권한 꺼내서 String 배열로 만듦
String[] strRole = { member.getUrole() };
System.out.println("=== MemberDetailServiceImpl/member => " + member);
// 권한을 n개 부여 가능하기때문에 Collection 타입으로 받는다
// String 배열로 만들어진 권한을 Collection<GrantedAuthority> 타입으로 변환함
Collection<GrantedAuthority> role = AuthorityUtils.createAuthorityList(strRole);
// 암호를 해시해서 넣어야 비교 가능
// SecurityConfig에서 설정
// 아이디, 암호, 권한'들'
// User user = new User(member.getUemail(), member.getUpw(), role);
MyUserDTO user = new MyUserDTO(member.getUemail(), member.getUpw(), role, member.getUphone(),
member.getUname());
System.out.println("=== MemberDetailServiceImpl/user => " + user);
return user; // User 모양으로 리턴하면 알아서 로그인처리를 하겠다
}
}
package com.example.mapper;
import com.example.dto.MemberDTO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface MemberMapper {
// security login
// SELECT 컬럼명들 FROM 테이블명 WHERE 조건 AND 조건;
@Select({ "SELECT UEMAIL, UPW, UNAME, UPHONE, UROLE FROM MEMBER ",
" WHERE UEMAIL = #{email}" })
public MemberDTO memberEmail(
@Param(value = "email") String em);
// login
// SELECT 컬럼명들 FROM 테이블명 WHERE 조건 AND 조건;
@Select({ "SELECT UEMAIL, UNAME, UROLE FROM MEMBER ",
" WHERE UEMAIL = #{email} AND UPW = #{pw}" })
public MemberDTO memberLogin(
@Param(value = "email") String em,
@Param(value = "pw") String pw);
// 파라미터 여거래 사용 가능 => 명칭부여 = @Param
// join
// INSERT INTO 테이블명(컬럼명) VALUES(추가할값);
@Insert({
" INSERT INTO MEMBER ",
" (UEMAIL, UPW, UNAME, UPHONE, UROLE, UREGDATE) ",
" VALUES(#{obj.uemail}, #{obj.upw}, #{obj.uname}, #{obj.uphone}, #{obj.urole}, CURRENT_DATE) " })
public int memberJoin(@Param(value = "obj") MemberDTO member);
}
BCryptPasswordEncoder bcrpt = new BCryptPasswordEncoder();
member.setUpw(bcrpt.encode(member.getUpw()));
package com.example.controller;
import javax.servlet.http.HttpSession;
import com.example.dto.MemberDTO;
import com.example.dto.MyUserDTO;
import com.example.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class SecurityController {
// Mapper 사용하겠다 선언
@Autowired
MemberMapper mMapper;
// 세션 사용 선언
@Autowired
HttpSession httpSession;
// 세명의 그룹(관리자, 판매자, 고객)으로 나눠서 각자의 권한을 부여
///////////////////////////////////////////////////
//////////////////// 로그인 전 ////////////////////
///////////////////////////////////////////////////
// 홈
@GetMapping(value = { "/security_home" })
public String securityhomeGET(
Model model,
@AuthenticationPrincipal MyUserDTO user) {
if (user != null) {
System.out.println("=== 아이디 : " + user.getUsername());
System.out.println("=== 이름 : " + user.getName());
System.out.println("=== 연락처 : " + user.getUserphone());
System.out.println("=== 권한 : " + user.getAuthorities().toArray()[0]);
}
model.addAttribute("user", user);
System.out.println("=== user => " + user);
// model.addAttribute("userid", user.getUsername());
// model.addAttribute("userrole", user.getAuthorities().toArray()[0]);
// 세션에 담긴 로그인한 사용자의 정보를 꺼내옴
return "/security/home";
}
// 권한없음 접근불가
@GetMapping(value = "/security_403")
public String security403GET() {
return "/security/security_403";
}
// 회원가입 GET
// 127.0.0.1:9090/ROOT/member/security_join
@GetMapping(value = "/member/security_join")
public String securityJoinGET() {
return "/security/join";
}
// 회원가입 POST
@PostMapping(value = "/member/security_join")
public String securityJoinPOST(
@ModelAttribute MemberDTO member) {
// crypt 객체생성
BCryptPasswordEncoder bcrpt = new BCryptPasswordEncoder();
// 암호를 가져와서 해시 한 후 다시 추가
member.setUpw(bcrpt.encode(member.getUpw()));
member.setUrole("CUSTOMER");
int ret = mMapper.memberJoin(member);
System.out.println("member => " + member);
// System.out.println("ret => " + ret);
if (ret == 1) {
// redirect는 주소를 변경한후에 엔터키를 누름
return "redirect:/security_home";
}
// redirect는 주소 변경 후 엔터 누르는 것과 같은 효과
return "redirect:/member/security_join";
}
// 로그인 GET
@GetMapping(value = "/member/security_login")
public String securityLoginGET() {
return "/security/login";
}
///////////////////////////////////////////////////
//////////////////// 로그인 후 ////////////////////
///////////////////////////////////////////////////
//////////////////// 관리자 ////////////////////
// 관리자 홈화면(로그인 후)
// 127.0.0.1:9090/ROOT/security_admin/home
@GetMapping(value = { "/security_admin/home" })
public String securityAdminhomeGET() {
return "/security/admin_home";
}
//////////////////// 판매자 ////////////////////
// 판매자 홈화면(로그인 후)
// 127.0.0.1:9090/ROOT/security_seller/home
@GetMapping(value = { "/security_seller/home" })
public String securitySellerhomeGET() {
return "/security/seller_home";
}
//////////////////// 고객 ////////////////////
// 고객 홈화면(로그인 후)
// 127.0.0.1:9090/ROOT/security_customer/home
@GetMapping(value = { "/security_customer/home" })
public String securityCustomerhomeGET() {
return "/security/customer_home";
}
}
package com.example.dto;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class MyUserDTO extends User {
private String username = null;
private String password = null;
private String userphone = null;
private String name = null;
public MyUserDTO(
String username,
String password,
Collection<? extends GrantedAuthority> authorities,
String userphone,
String name) {
// 상속받아도 부모가 수행하던것은 똑같이 해야한다
// 그리고 추가 한다
super(username, password, authorities);
this.username = username;
this.password = password;
this.userphone = userphone; // 연락처
this.name = name; // 이름
}
}