I can do it(Feat. DEV)

Spring Security 멀티 로그인 구현하기 본문

개발자 모드/응용

Spring Security 멀티 로그인 구현하기

까짓거 해보자 개발자 2022. 11. 8. 14:18
728x90

2022.11.03 - [개발자 모드/Spring Security] - Spring Security 사용하여 로그인 구현하기

 

 

Spring Security 사용하여 로그인 구현하기

다들 아시다시피 Spring Security 5.7.x 버전 이후부터 WebSecurityConfigurerAdapter가 deprecate(사용되지 않게) 될 예정이기 때문에 Spring Security에서 지향하는 SecurityFilterChain을 @Bean으로 등록하는 방식으로 진

precious-value.tistory.com

 

에 이어서 이번엔 관리자, 사용자 각각의 화면과 로그인 기능을 구현해보려고 함.

 

필자도 공부하면서 진행하는 거라서 틀린 점 있을 수 있음!! 알려주시면 감사하겠음!!!

 


 

1. 개발 스펙

- Spring Boot 2.7.3

- H2 DB

- Java 1.8

- Spring Security 5.7.3

- Maven 3.6.3

- Intellij IDEA 2021.2

- JSP

- JPA

 


 

2. 로그인 Front단 구현

 

2-1. 사용자 로그인 화면 구현(jsp)

 

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<jsp:include page="template/header.jsp"></jsp:include>	<!--헤더 부분-->
    <h1>사용자 로그인</h1>
    <form action="/login" method="post">
        <span>id : </span><input type="text" name="username" required />
        <span>password : </span><input type="password" name="password" required/>

        <input type="submit" value="로그인" />
    </form>
</body>
</html>

 

로그인 기능에 집중해서 화면 구현은 최대한 간단하게 진행했음.

 


 

2-1-1. 사용자 로그인 화면

 

사용자 로그인 화면
사용자 로그인 화면(header부분은 신경안써도 됨.)

 


 

2-2. 관리자 로그인 화면 구현(jsp)

 

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<jsp:include page="template/header.jsp"></jsp:include>	<!--헤더 부분-->
    <h1>관리자 로그인</h1>
    <form action="/admin/login" method="post">
        <span>id : </span><input type="text" name="username" required />
        <span>password : </span><input type="password" name="password" required />

        <input type="submit" value="로그인" />
    </form>
</body>
</html>

 

이런 식으로 form 태그를 작성하고 주의할 점!

 

1. form action 경로는 security config에서 loginProcessingUrl("경로")와 일치해야 한다는 점

 

2. id, password input 태그의 name 값을 username, password 이외에 다른 걸 사용할 땐 security config login 부분에  따로

 

설정해야 함.(name 값을 username, password로 설정 시 설정 안 해도 됨)

 

참고하시면 될 듯.

 


 

2-2-1. 관리자 로그인 화면

 

관리자 로그인 화면
관리자 로그인 화면

 

 

 


 

3. 로그인 Back단 구현

 

3-1. 사용자 Controller 구현

 

구현하기 앞서 필자처럼 꼭 관리자, 사용자 Controller를 나누지 않고,

 

한 Controller에서 관리해도 상관없음.

 

필자는 나중에 기능이 추가되면 결국 나눠야 해서 미리 분리하였음.

 

@Controller
@RequestMapping
public class UserMainController {

    private final Logger logger = LoggerFactory.getLogger(UserMainController.class);

    @GetMapping("/")
    public String main(Model model){
        model.addAttribute("users", userRepository.findAll());
        return "user/index";
    }

    @GetMapping("/login")
    public String login(){
        logger.info("로그인화면");
        return "user/login";
    }
}

 


 

3-2. 관리자 Controller 구현

 

@Controller
@RequestMapping("/admin")
public class AdminMainController {

    private final Logger logger = LoggerFactory.getLogger(AdminMainController.class);

    @GetMapping
    public String main(Model model){
        return "admin/index";
    }

    @GetMapping("/login")
    public String login(){
        logger.info("관리자 로그인화면");
        return "admin/login";
    }
}

 


 

3-3. 사용자 UserDetailService

 

public class UserDetailService implements UserDetailsService {

    private final Logger logger = LoggerFactory.getLogger(UserDetailService.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException{
        logger.info("userId === "+userId);
        User user = userRepository.findOneById(userId);
        if(user == null){
            throw new UsernameNotFoundException("해당 유저가 없습니다.");
        }

        ExamUser examUser = new ExamUser(user, buildAdminAuthority());

        return examUser;
    }

    private Set<GrantedAuthority> buildAdminAuthority() {	//권한 부여 로직

        Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

        setAuths.add(new SimpleGrantedAuthority("ROLE_USER"));

        return setAuths;
    }
}

 

필자 같은 경우 UserDetails를 커스텀할 필요가 없어서 UserDetails를 직접 구현하기보다는 

 

User라는 UserDetails를 구현해놓은 구현체를 확장한 ExamUser 객체를 만들어서 사용함.(관리자도 동일)

 


 

3-4. 사용자 ExamUser

 

@Getter
@Setter
public class ExamUser extends User {
    String id;
    String name;

    public ExamUser(com.exam.domain.user.User user, Collection<? extends GrantedAuthority> authorities) {
        super(user.getId(), user.getPassword(), authorities);
        this.id = user.getId();
        this.name = user.getName();
    }
}

 


 

3-5. 관리자 UserDetailsService

 

public class AdminDetailService implements UserDetailsService {

    private final Logger logger = LoggerFactory.getLogger(AdminDetailService.class);

    @Autowired
    private AdminRepository adminRepository;

    @Override
    public UserDetails loadUserByUsername(String adminId) throws UsernameNotFoundException{
        logger.info("adminId === "+adminId);
        Admin admin = adminRepository.findOneById(adminId);
        if(admin == null){
            throw new UsernameNotFoundException("해당 관리자가 없습니다.");
        }
        ExamAdmin examAdmin = new ExamAdmin(admin, buildAdminAuthority());

        return examAdmin;
    }

    private Set<GrantedAuthority> buildAdminAuthority() {

        Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

        setAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        return setAuths;
    }
}

 

클래스 명칭이나 메소드 명은 필자 임의로 설정한 것임! 

 

@Override 된 메소드는 구현 메소드이므로 똑같은 명칭 쓰기! 다들 아시죠?👍

 


 

3-6. 관리자 ExamAdmin

 

@Getter
@Setter
public class ExamAdmin extends User {
    String id;
    String name;

    public ExamAdmin(Admin admin, Collection<? extends GrantedAuthority> authorities) {
        super(admin.getId(), admin.getPassword(), authorities);
        this.id = admin.getId();
        this.name = admin.getName();
    }
}

 

이런 식으로 권한과 아이디, 이름을 세팅하는 생성자를 만들어서 사용하였음.

 


 

3-7. 사용자 Security Config

 

@Configuration
@Order(2)
public class UserSecurityConfiguration {

    @Bean
    public UserDetailsService userDetailsService(){
        return new UserDetailService();
    }

    @Bean
    public PasswordEncoder userPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider userAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService());
        provider.setPasswordEncoder(userPasswordEncoder());

        return provider;
    }

    @Bean
    public SecurityFilterChain userFilterChain(HttpSecurity http) throws Exception {
        http
                .authenticationProvider(userAuthenticationProvider())	//userAuthenticationProvider등록
                .antMatcher("/**")
                .authorizeHttpRequests(authorize -> authorize     
                        .anyRequest().permitAll())
                .formLogin(form -> form
                        .loginPage("/login")
                        .loginProcessingUrl("/login")
                        .failureUrl("/login?error=true")
                        .defaultSuccessUrl("/")
                        .permitAll())
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/"))
                .csrf(csrf -> csrf.disable());	//csrf 토근은 초기 개발과정에는 불필요해서 미사용처리
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/h2-console/**");
    }
}

 

상단부터 설명하겠음.

@Order(숫자)는 security 설정 파일이 2개라서 어떤 설정 파일을

먼저 읽을 것인지 순서를 정하는 것이라고 보면 될 듯. 순서는 자유롭게 설정하시길.

userDetailsService, userPasswordEncoder, userAuthenticationProvider 차례대로 bean을 등록함.

 

DaoAuthenticationProvider는 AbstractUserDetailsAuthenticationProvider를 상속받아 실제 인증 과정에 대한 로직을 처리하는데, 사용할 userDetailsService와 passwordEncoder을 세팅함.

 

위에서 만든 provider을 filterChain에 등록하고 사용자는 모든 경로를 접근 허용으로 설정하였음.

이후 formLogin, logout 설정을 진행함.

 

WebSecurityCustomizer는 해당 경로는 spring security를 적용하지 않겠다는 설정인데 

필자는 h2 db를 사용하고 있어서 설정하였음. 필요없으면 굳이 안 해도 됨!

 

관련 설명은 하단에 참고 사이트 보기!

 


 

3-8. 관리자 Security Config

 

@Configuration
@Order(1)
public class AdminSecurityConfiguration {

    @Bean
    public UserDetailsService adminDetailsService(){
        return new AdminDetailService();
    }

    @Bean
    public PasswordEncoder adminPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider adminAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(adminDetailsService());
        provider.setPasswordEncoder(adminPasswordEncoder());

        return provider;
    }

    @Bean
    public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {
        http
                .authenticationProvider(adminAuthenticationProvider())
                .antMatcher("/admin/**")
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest()
                        .hasAnyAuthority("ROLE_ADMIN"))
                .formLogin(form -> form
                        .loginPage("/admin/login")
                        .loginProcessingUrl("/admin/login")
                        .failureUrl("/admin/login?error=true")
                        .defaultSuccessUrl("/admin")
                        .permitAll())
                .logout(logout -> logout
                        .logoutUrl("/admin/logout")
                        .logoutSuccessUrl("/admin"))
                .csrf(csrf -> csrf.disable());

        return http.build();
    }
}

 

관리자 설정도 사용자와 크게 다르지 않다는 것을 볼 수 있음.

다만 필자는 filterChain 부분에 권한을 다르게 주었는데,

이 부분은 개발하시는 분이 자유롭게 설정하시면 될 것 같음!

 

이상으로 Spring Security를 사용한 멀티 로그인 구현 끝!!

오늘도 다들 화이팅하시고 건강한 하루 보내시길😉

 


 

📢참고 사이트

https://velog.io/@dnjscksdn98/Spring-Spring-Security%EB%9E%80

 

[Spring] Spring Security란?

Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 Spring 하위 프레임워크입니다.Spring Security는 "인증" 과 "권한" 에 대한 부분을 Filter의 흐름에 따라 처리합니다.S

velog.io

https://abbo.tistory.com/179

 

@Order 를 사용하여 보안 필터링 적용하기

Spring 4 에서 새로 소개된 @Order 어노테이션은 같은 타입의 Bean이 Collection에 Autowired 될 때 순서를 지정하기 위해 사용합니다. 아무래도 인증이나 보안과 관련되어 가장 우선적으로 필터링할 클래

abbo.tistory.com

https://www.codejava.net/frameworks/spring-boot/multiple-login-pages-examples

 

Spring Security Multiple Login Pages Examples

 

www.codejava.net

 

728x90