이론편 보기
2020/06/28 - [spring boot] - [spring security] spring boot에 가장 빠르게 적용해 보자[1]
일단 DB에 접근할 수 있는 환경과 더불어 기본적인 mvc 패턴 vo,dao 정도로 구성이 다 짜여있다는 바탕 하에 진행해 보도록 하겠습니다.
저는 springboot + mybatis + mysql만으로 구현했습니다.
또한 필자는 다른 블로그에 비해 지식이 한없이 얕기 때문에 jpa를 통해 db에 접근한다던지던 지 enum을 사용해 사용자에게 권한을 준다 던지 이런 기술들은 제게 없습니다 어떻게 보면 가장 베이직한 기술만으로 적용을 했습니다.
필요한 파일:
WebSecurityConfigurerAdapter 상속한 class 1개
UserDetails를 구현한 class 1개
UserDetailService를 구현한 class 1개
+@
진행하기에 앞서 인터넷에서 검색한 코드를 보다보면 어디서부터 온건지 뭔지 몰라 화가 나는 경우가 있었기 때문에
전체적인 구성을 올리겠습니다.
아직 완성되어있지 않은 토이 프로젝트라 감안하고 보시길 바랍니다.
각설하고 일단 springboot에 gradle로 라이브러리를 적용해 줍시다.
(maven으로 하시는 경우 maven repository으로 찾아봅시다 조금만 찾아보면 간단합니다)
build.gradle
compile 'org.springframework.boot:spring-boot-starter-security'
SecurityConfig.java (WebSecurityConfigurerAdapter)
package com.blackzapato.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.blackzapato.demo.service.SecurityService;
@Configuration //이 클래스를 통해 bean 등록이나 각종 설정을 하겠다는 표시
@EnableWebSecurity // Spring Security 설정할 클래스라고 정의합니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SecurityService homeService;
@Bean //회원가입시 비번 암호화에 필요한 bean 등록
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean //실제 인증을 한 이후에 인증이 완료되면 Authentication객체를 반환을 위한 bean등록
public DaoAuthenticationProvider authenticationProvider(SecurityService homeService) {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(homeService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
@Override
public void configure(WebSecurity web) throws Exception {
// static 디렉터리의 하위 파일 목록은 인증 무시 ( = 항상통과 )
web.ignoring().antMatchers("/resource/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")// 관리자페이지를 위한 곳
.antMatchers("/**").permitAll()
.anyRequest().authenticated()// 여기까지는 기본적으로 경로를 어떻게 사용할 것인가를 위한 곳이다
.and()
.csrf().ignoringAntMatchers("/admin/goods/ckUpload") //기본적으로 springSecurity에선 post로 controller로 정보를 보내줄떼 csrf라는 토큰이 필요하다 이것을 무시하기위한 경로
.and()
.formLogin()
.loginPage("/member/signin")
.failureUrl("/member/signin?error") /*로그인 실패시 url*/
.defaultSuccessUrl("/", true) /*로그인 성공시 url*/
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessUrl("/home")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider(homeService));
}
}
MemberPrincipalVO.java (userDetails)
package com.blackzapato.demo.dto;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@SuppressWarnings("serial")
public class MemberPrincipalVO implements UserDetails{
private ArrayList<MemberVO> userVO;
public MemberPrincipalVO(ArrayList<MemberVO> userAuthes) {
this.userVO = userAuthes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() { //유저가 갖고 있는 권한 목록
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(int x=0; x<userVO.size(); x++) {
authorities.add(new SimpleGrantedAuthority(userVO.get(x).getRoleName()));
}
return authorities;
}
@Override
public String getPassword() { //유저 비밀번호
return userVO.get(0).getUserPass();
}
@Override
public String getUsername() {// 유저 이름 혹은 아이디
return userVO.get(0).getUserName();
}
@Override
public boolean isAccountNonExpired() {// 유저 아이디가 만료 되었는지
return true;
}
@Override
public boolean isAccountNonLocked() { // 유저 아이디가 Lock 걸렸는지
return true;
}
@Override
public boolean isCredentialsNonExpired() { //비밀번호가 만료 되었는지
return true;
}
@Override
public boolean isEnabled() { // 계정이 활성화 되었는지
return true;
}
}
SecurityService.java (UserDetailsService)
package com.blackzapato.demo.service;
import java.util.ArrayList;
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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.blackzapato.demo.dao.MemberMapper;
import com.blackzapato.demo.dto.MemberPrincipalVO;
import com.blackzapato.demo.dto.MemberVO;
@Service
public class SecurityService implements UserDetailsService{
@Autowired
private MemberMapper homeMapper;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
//DB에서 유저정보를 불러온다. Custom한 Userdetails 클래스를 리턴 해주면 된다.(실질적인 로그인코드)
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
System.out.println("id : "+id);
ArrayList<MemberVO> userAuthes = homeMapper.findByUserId(id);
System.out.println("userAuthes size : "+userAuthes.size());
if(userAuthes.size() == 0) {
throw new UsernameNotFoundException("User "+id+" Not Found!");
}
return new MemberPrincipalVO(userAuthes);
}
//회원가입도중 오류가 날수 있으므로 transactional을 사용합니다 모르면 구글갓에게 물어봅시다
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public String InsertUser(MemberVO userVO) throws Exception {
userVO.setUserPass(bCryptPasswordEncoder.encode(userVO.getUserPass()));
int flag = homeMapper.signup(userVO);
System.out.println("flag num : "+flag);
if (flag > 0) {
userVO.setRoleName("USER");
int roleNo = homeMapper.findRoleNo(userVO.getRoleName());
System.out.println("roleNo : "+roleNo);
String id = userVO.getUserId();
homeMapper.userRoleSave(id, roleNo);
return "success";
}
return "fail";
}
}
HomeController.java
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@Autowired
MemberMapper mm;
@Autowired
SecurityService hs;
@GetMapping("/") //로그인 성공시 위에서 설정해준대로 맵핑이 되기때문에 이곳만 보여드리겠습니다.
//다른 부분은 그냥 하시던 그대로 하시면 됩니다
public String loadExceptionPage(HttpServletRequest req, RedirectAttributes rtt){
logger.info("signin after contoller");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
MemberPrincipalVO userPrincipalVO = (MemberPrincipalVO) auth.getPrincipal();
//위의 두줄로 로그인 한 계정이 어떤 권한을 가지고 있는지 이름은 무었인지를 가져옵니다
HttpSession session = req.getSession();
System.out.println("username : "+userPrincipalVO.getUsername());
System.out.println("authorities : "+userPrincipalVO.getAuthorities());
session.setAttribute("member", userPrincipalVO.getUsername());
session.setAttribute("principal", userPrincipalVO.getAuthorities());
return "home";
}
}
이 정도면 실행되리라 생각합니다.
끝으로 적용은 했지만 더 자세히 알고 싶다 하시는 분들을 위해..
https://hyunsangwon93.tistory.com/24
https://victorydntmd.tistory.com/328
https://coding-start.tistory.com/153
https://myserena.tistory.com/158