스프링 부트를 공부하면서 스프링에서 제공하는 spring security를 통해 로그인 기능을 편리하게 구현할 수 있다는 것을 알았다.

기존에 회사에서는 interceptor 기능을 통해서 로그인 기능을 만들어서 사용했다. 둘다 좋은 방식이지만 spring security를 사용하면 csrf 공격도 막을 수 있어서 한번 사용해봐도 좋을 것 같다.

1. 라이브러리 pom.sml



1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
cs




2. SpringSecurityAdaptor


-> 스프링 시큐리티에 필요한 내용을 정의하는 configuration을 생성해야한다.
-> WebSecurityConfigurerAdapter 클래스를 상속받아서 configure 메서드를 재정의 해야한다.
-> @EnableWebSecurity, @EnableGlobalAuthentication와 같은 애노테이션을 사용하여 스프링 시큐리티 사용을 정의 해야 한다.
-> public void configure(WebSecurity web) throws Exception 메서드를 재정의하여 로그인 상관 없이 허용을 해줘야할 리소스 위치를 정의한다.
-> protected void configure(HttpSecurity http) throws Exception  메소드를 재정의하여 로그인 URL, 권한분리, logout URL 등등을 설정할 수 있다. (자세한 설명은 메서드에 주석으로 확인)




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.wedul.common.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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.web.util.matcher.AntPathRequestMatcher;
 
import com.wedul.wedulpos.user.serviceImpl.AuthProvider;
 
/**
 * Security Configuration
 * 
 * @author wedul
 *
 */
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication
@ComponentScan(basePackages = {"com.wedul.*"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    AuthProvider authProvider;
    
    @Autowired
    AuthFailureHandler authFailureHandler;
 
    @Autowired
    AuthSuccessHandler authSuccessHandler;
 
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 허용되어야 할 경로들
        web.ignoring().antMatchers("/resources/**"
                                   "/dist/**"
                                   "/weather"
                                   "/user/password/find",
                                   "/user/join",
                                   "/user/email",
                                   "/user/send/temppw",
                                   "/findpw"
                                   "/user/findpw",
                                   "/user/cert/check",
                                   "/join"
                                   "/getLanguage/**",
                                   "/getMessage"); // #3
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        // 로그인 설정
        http.authorizeRequests()
            // ROLE_USER, ROLE_ADMIN으로 권한 분리 유알엘 정의
            .antMatchers("/""/user/login""/error**").permitAll()
            .antMatchers("/**").access("ROLE_USER")
            .antMatchers("/**").access("ROLE_ADMIN")
            .antMatchers("/admin/**").access("ROLE_ADMIN")
            .antMatchers("/**").authenticated()
        .and()
            // 로그인 페이지 및 성공 url, handler 그리고 로그인 시 사용되는 id, password 파라미터 정의
            .formLogin()
            .loginPage("/user/login")
            .defaultSuccessUrl("/")
            .failureHandler(authFailureHandler)
            .successHandler(authSuccessHandler)
            .usernameParameter("id")
            .passwordParameter("password")
        .and()    
            // 로그아웃 관련 설정
            .logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
            .logoutSuccessUrl("/")
            .invalidateHttpSession(true)
        .and()
            // csrf 사용유무 설정
            // csrf 설정을 사용하면 모든 request에 csrf 값을 함께 전달해야한다.
            .csrf()
        .and()
            // 로그인 프로세스가 진행될 provider
            .authenticationProvider(authProvider);
    }
 
}
cs



3. 로그인 검증을 진행하는 AuthProvider
-> AuthenticationProvider 인터페이스를 구현



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.wedulpos.user.serviceImpl;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
 
import com.wedul.common.util.Constant;
import com.wedul.common.util.HashUtil;
import com.wedul.wedulpos.user.dto.MyAuthenticaion;
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
/**
 * 인증 프로바이더
 * 로그인시 사용자가 입력한 아이디와 비밀번호를 확인하고 해당 권한을 주는 클래스
 * 
 * @author wedul
 *
 */
@Component("authProvider")
public class AuthProvider implements AuthenticationProvider  {
    
    @Autowired
    UserService userService;
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String id = authentication.getName();
        String password = HashUtil.sha256(authentication.getCredentials().toString());
        
        UserDto user = userService.selectUser(new UserDto(id));
        
        // email에 맞는 user가 없거나 비밀번호가 맞지 않는 경우.
        if (null == user || !user.getPassword().equals(password)) {
            return null;
        }
        
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        
        // 로그인한 계정에게 권한 부여
        if (user.isIsadmin()) {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_ADMIN.toString()));
        } else {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_USER.toString()));
        }
 
        // 로그인 성공시 로그인 사용자 정보 반환
        return new MyAuthenticaion(id, password, grantedAuthorityList, user);
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
 
}
cs




4. 로그인 성공, 실패 후 진행되는 handler 클래스
-> 로그인 성공 그리고 실패 후 진행될 클래스 정의



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.common.config;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 실패 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 실패 시 response를 json 형태로 결과값 전달
        response.getWriter().print("{\"success\": false}");
        response.getWriter().flush();
    }
}
 
 
package com.wedul.common.config;
 
import java.io.IOException;
 
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.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 성공 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
 
        response.setStatus(HttpServletResponse.SC_OK);
        // 성공 시 response를 json형태로 반환
        response.getWriter().print("{\"success\": true}");
        response.getWriter().flush();
    }
 
}
cs




스프링 시큐리티를 처음 적용해 볼때는 어려움도 많았지만 적용하니 엄청 간단하게 사용할 수 있어서서 좋았다. 적용된 소스코드에 대한 정확한 정보는 https://github.com/weduls/wedulpos_boot 여기서 확인 할 수 있다.



  1. 지나가는사람 2018.12.20 11:01

    MyAuthenticaion 클래스랑 . userService 클래스랑 ,Constant.ROLE_TYPE.ROLE_ADMIN 이부분 의 소스 UserDTO부분좀 요청 드려도될까요 ??

  2. Favicon of https://daesoop.tistory.com BlogIcon 김대숲 2019.06.17 13:32 신고

    글 너무 잘 읽었습니다.
    읽던 도중 깃헙 확인 중에 core부분이 gitignore 되어 있는것 같은데 혹시 저도 위에 분과 같이 소스좀 요청드려도 괜찮을까요?

    • Favicon of https://wedul.site BlogIcon 위들 wedul 2019.06.17 20:57 신고

      안녕하세요.
      우선 도움이 되었다니 감사합니다.

      https://github.com/weduls/wedulpos_boot/blob/master/core/src/main/java/com/wedul/wedulpos/core/user/dto/MyAuthenticaion.java

      필요하신 코드가 어떤 코드인지 모르겠으나, 다 있는것 같아요.

      혹시 필요한 별도 클래스가 있으시다면 찾아드리겠습니다.

+ Recent posts