본문 바로가기

관리 메뉴

Spring 3.1.1 Security 로그인 및 로그인 권한 설정 본문

Spring

Spring 3.1.1 Security 로그인 및 로그인 권한 설정

SaintsP 2019. 2. 6. 13:55

Spring 3.1.1 기반으로 Security 로그인 및 로그인 권한 설정을 해보겠습니다.




가장 먼저 pom.xml에 Spring Security와 Spring Security Taglibs을 추가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- Spring-Security -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
 
<!-- Spring-Security-Taglibs -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
                
cs




다음으로 web.xml에 security context를 등록합니다.


1
2
3
4
5
6
7
8
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/root-context.xml
        /WEB-INF/spring/security-context.xml
        </param-value>
</context-param>
 
cs




web.xml에 등록한 security-context.xml 파일을 작성합니다.


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
<?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/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    
    <!-- @Secured 어노테이션 설정 -->
    <security:global-method-security secured-annotations="enabled" proxy-target-class="true" />
    
    <!-- 로그인, 로그아웃 및 권한별 접근 url 설정. -->
    <security:http use-expressions="true" authentication-manager-ref="authenticationManager">
        
        <!-- url별 접근 권한 설정 -->
        <security:intercept-url pattern="/**" access="permitAll" /
        
        <!-- 로그인 설정 -->
        <security:form-login login-page="/login.do" username-parameter="user_id" password-parameter="user_pw"
            default-target-url="/" login-processing-url="/loginCheck" />
        
        <!-- 로그아웃 설정 -->
        <security:logout logout-url="/logout" />
        
    </security:http>
    
    <!-- DB에서 불러올 사용자 정보 및 권한 설정  -->
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="customAuthenticationProvider" /<!-- 권한 설정할 커스텀 Bean -->
        <security:authentication-provider user-service-ref="customUserDetailsService"<!-- 사용자 정보 설정할 커스텀 Bean -->
            <security:password-encoder ref="passwordEncoder"<!-- 로그인 비밀번호 암호화 인코딩 Bean -->
                <security:salt-source ref="saltSource" /<!-- 로그인 비밀번호 암호화 salt Bean -->
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>
    
    <!-- 권한 설정할 커스텀 Bean -->    
    <bean id="customAuthenticationProvider" class="com.parksc.myHome.security.CustomAuthenticationProvider">
    </bean>
    
    <!-- 사용자 정보 설정할 커스텀 Bean -->
    <bean id="customUserDetailsService" class="com.parksc.myHome.security.CustomUserDetailsService">
    </bean>
    
    <!-- 로그인 암호 인코딩 Bean -->
    <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
    
    <!-- 로그인 비밀번호 암호화 salt Bean -->    
    <bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
        <property name="userPropertyToUse" value="user_id" />
    </bean>
    
    
</beans>
 
cs



26번 ~ 34번 라인이 이번 설정에 핵심부분인데요, DB에 저장된 유저 정보와 권한을 설정해 주는 부분입니다. 


암호화도 같이 사용하고 있는데요, user_id를 salt로 하는 Md5 방식의 암호화를 사용했습니다. 요즘은 Md5 대신 Sha128를 사용하는 추세입니다만, 여기서는 제일 간단한 암호화 방식 중 하나인 Md5로 암호화를 진행하였습니다.



권한 설정하는 부분을 보기 전 사용자 정보를 설정하는 부분부터 보겠습니다.



사용자 정보를 설정하는 클래스인 CustomUserDetailsService 클래스를 보시면,


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
package com.parksc.myHome.security;
 
import javax.inject.Inject;
 
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;
 
import com.parksc.myHome.persistence.MemberDAO;
 
@Service
public class CustomUserDetailsService implements UserDetailsService {
 
    @Inject
    MemberDAO dao;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userInfo = null;
        try {
            int member_id = dao.selectMemberID(username);
            userInfo = (UserDetails) dao.selectMember(member_id); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return userInfo;
    }
    
 
}
 
cs



CustomUserDetailsService는 UserDetailsService를 구현한 클래스로 loadUserByUsername메소드를 오버라이딩 합니다.


간단하게 MemberDAO에서 member_id를 가지고 유저 정보를 불러오면 해당 유저의 정보를 UserDetails라는 스프링에서 제공하는 클래스로 반환하는 메소드인데요, 내가 설정한 User 정보(VO)를 스프링이 제공하는 UserDetails를 변환 하기 위해서는 VO가 UserDetails를 구현하고 있어야 합니다. 


제 코드에서는 dao.selectMember(member_id)는 MemberVO를 반환하지만, 제가 만든 MemberVO는 UserDetails를 구현하도록 만들었기 때문에 UserDetails로 형변환이 가능합니다. 


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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.parksc.myHome.domain;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
 
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
public class MemberVO implements UserDetails{
 
    private int member_id;
    private String user_id;
    private String user_pw;
    private String user_email;
    private Date create_date;
    private Date update_date;
    private boolean act_flg;
    private boolean del_flg;
    private String grant_name;
    
    public int getMember_id() {
        return member_id;
    }
    public void setMember_id(int member_id) {
        this.member_id = member_id;
    }
    public String getUser_id() {
        return user_id;
    }
    public void setUser_id(String user_id) {
        this.user_id = user_id;
    }
    public String getUser_pw() {
        return user_pw;
    }
    public void setUser_pw(String user_pw) {
        this.user_pw = user_pw;
    }
    public String getUser_email() {
        return user_email;
    }
    public void setUser_email(String user_email) {
        this.user_email = user_email;
    }
    public Date getCreate_date() {
        return create_date;
    }
    public void setCreate_date(Date create_date) {
        this.create_date = create_date;
    }
    public Date getUpdate_date() {
        return update_date;
    }
    public void setUpdate_date(Date update_date) {
        this.update_date = update_date;
    }
    public boolean isAct_flg() {
        return act_flg;
    }
    public void setAct_flg(boolean act_flg) {
        this.act_flg = act_flg;
    }
    public boolean isDel_flg() {
        return del_flg;
    }
    public void setDel_flg(boolean del_flg) {
        this.del_flg = del_flg;
    }
    
    public String getGrant_name() {
        return grant_name;
    }
    public void setGrant_name(String grant_name) {
        this.grant_name = grant_name;
    }
    @Override
    public String toString() {
        return "MemberVO [member_id=" + member_id + ", user_id=" + user_id + ", user_pw=" + user_pw + ", user_email="
                + user_email + ", create_date=" + create_date + ", update_date=" + update_date + ", act_flg=" + act_flg
                + ", del_flg=" + del_flg + "]";
    }
    @Override
    public Collection<extends GrantedAuthority> getAuthorities() {
        
        String roleGrant = "ROLE_" + grant_name;
        
        GrantedAuthority myGrant = new SimpleGrantedAuthority(roleGrant);
        
        List<GrantedAuthority> authorities = new ArrayList<>();
        
        authorities.add(myGrant);
        
        return authorities;
    }
    @Override
    public String getPassword() {
        // TODO Auto-generated method stub
        return user_pw;
    }
    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return user_id;
    }
    @Override
    public boolean isAccountNonExpired() {
        // TODO Auto-generated method stub
        return false;
    }
    @Override
    public boolean isAccountNonLocked() {
        // TODO Auto-generated method stub
        return false;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        return false;
    }
    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        return false;
    }
    
}
 
cs




VO에서 UserDetails를 구현하면 몇가지 메소드들을 오버라이딩 해야 되는데요, 기본적으로 다음 3가지 메소드를 보시겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    @Override
    public Collection<extends GrantedAuthority> getAuthorities() {
        
        String roleGrant = "ROLE_" + grant_name;
        
        GrantedAuthority myGrant = new SimpleGrantedAuthority(roleGrant);
        
        List<GrantedAuthority> authorities = new ArrayList<>();
        
        authorities.add(myGrant);
        
        return authorities;
    }
    @Override
    public String getPassword() {
        // TODO Auto-generated method stub
        return user_pw;
    }
    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return user_id;
    }
cs



getUsername()과 getPassword()는 id와 비밀번호를 반환하는 메소드이구요, getAuthorities()는 해당 사용자의 권한을 GrantedAuthority나 GrantedAuthority의 자식 클래스를 제네릭으로 갖는 컬렉션을 반환하는 메소드입니다. 제 코드에서는 SimpleGrantedAuthority라는 메소드를 사용하여 myGrant를 생성하여 리스트에 담은것을 반환하도록 하였습니다.


Context에서 변경할 수 있지만 기본적으로 Spring Security의 권한은 "ROLE_"로 시작하므로, 사용자가 갖는 최종 권한은 ADMIN, USER가 아닌 ROLE_ADMIN, ROLE_USER가 됩니다. (ADMIN과 USER는 제가 DB에 저장하는 권한인 grant_name입니다.) 



다음으로 제일 중요한 부분인 CustomAuthenticationProvider 클래스를 보시겠습니다.


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
package com.parksc.myHome.security;
 
import java.util.List;
 
import javax.annotation.Resource;
 
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
 
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
 
    @Resource
    CustomUserDetailsService customUserDetailsService;
    
    @Resource
    private Md5PasswordEncoder pe;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication; 
 
        UserDetails userInfo = customUserDetailsService.loadUserByUsername(authToken.getName()); 
        if (userInfo == null) {
          throw new UsernameNotFoundException(authToken.getName());
        }
 
        if (!matchPassword(userInfo.getPassword(), authToken.getCredentials(), authToken.getName())) {
          throw new BadCredentialsException("not matching username or password");
        }
 
        List<GrantedAuthority> authorities = (List<GrantedAuthority>) userInfo.getAuthorities();
        
        return new UsernamePasswordAuthenticationToken(userInfo,null,authorities);
    }
 
    private boolean matchPassword(String password, Object credentials, String inName) {
        String encodedPw = pe.encodePassword((String) credentials, inName);
        return password.equals(encodedPw);
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
         return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
 
}
 
cs



CustomAuthenticationProvider는 AuthenticationProvider를 구현한 클래스로 authenticate과 supports메소드를 오버라이딩 합니다.


authenticate메소드는 사용자가 입력한 ID와 PW를 가지고 실제 DB와 비교하여 값이 일치하면 해당 사용자의 정보를 담은 Authentication이라는 클래스를 리턴합니다. 위에 CustomUserDetailsService에서 오버라이딩한 loadUserByUsername메소드를 호출하는 곳이기도 합니다.


supports는 입력받은 class가 Authentication를 구현했는지 검사하는 메소드입니다. 



이렇게 설정하면 기본적인 Spring Security의 로그인 및 권한 설정은 끝났습니다.




JSP단에서 권한별로 노출을 달리 해주고 싶은 경우나 로그인한 사용자의 정보를 불러오고 싶을 때에는 Spring Security Taglibs을 사용하면 편리합니다.



제가 테스트로 사용중인 JSP의 일부분을 보시면,


<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %> 선언을 해 주신 후,


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
        <security:authorize ifAnyGranted="ROLE_ADMIN">    
             <!-- Navbar btn-group -->
              <div class="navbar-btn-group btn-group navbar-right no-margin-r-xs">
                
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                    <a class="btn btn-outline" style="text-transform: none;">[ <security:authentication property="principal.user_id" var="loginID"/>${loginID } ]님 어서오세요.</a>
                </div>
                <!-- /Btn Wrapper -->
                
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                
                  <a class="btn btn-outline"> 관리자페이지</a>
                  
                </div>
                <!-- /Btn Wrapper -->
                
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                
                  <a class="btn btn-outline" href="/logout"><i class="ti ti-user"></i> Logout</a>
                  
                </div>
                <!-- /Btn Wrapper -->
                
               </div>
               <!-- /Navbar btn-group -->
            </security:authorize>
            <security:authorize ifAnyGranted="ROLE_USER">    
             <!-- Navbar btn-group -->
              <div class="navbar-btn-group btn-group navbar-right no-margin-r-xs">
              
                  <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                    <a class="btn btn-outline" style="text-transform: none;">[ <security:authentication property="principal.user_id" var="loginID"/>${loginID } ]님 어서오세요.</a>
                </div>
                <!-- /Btn Wrapper -->
                
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                
                  <a class="btn btn-outline" href="/logout"><i class="ti ti-user"></i> Logout</a>
                  
                </div>
                <!-- /Btn Wrapper -->
                
              </div>
              <!-- /Navbar btn-group -->
            </security:authorize>
            <security:authorize ifNotGranted="ROLE_ADMIN, ROLE_USER">
              <!-- Navbar btn-group -->
              <div class="navbar-btn-group btn-group navbar-right no-margin-r-xs">
              
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                
                  <a class="btn btn-outline" href="/login.do"><i class="ti ti-user"></i> Login</a>
                  
                </div>
                <!-- /Btn Wrapper -->
 
                <!-- Btn Wrapper -->
                <div class="btn-wrapper dropdown">
                
                  <a class="btn btn-outline" href="/member/register.do"><i class="ti ti-pencil-alt"></i> Join</a>
                  
                </div>
                <!-- /Btn Wrapper -->
 
              </div>
              <!-- /Navbar btn-group -->
            </security:authorize>
cs



<security:authorize ifAnyGranted="ROLE_ADMIN"> ... </security:authorize>


<security:authorize ifAnyGranted="ROLE_USER"> ... </security:authorize>  


<security:authorize ifNotGranted="ROLE_ADMIN, ROLE_USER"> ... </security:authorize>


를 통해 권한 별 노출을 설정할 수 있구요, 



<security:authentication property="principal.user_id" var="loginID"/>${loginID } 


를 통해 로그인 된 사용자 정보를 쉽게 불러올 수도 있습니다. 




- 비로그인 상태



- ADMIN 로그인 상태


- USER 권한 로그인 상태




혹시나 잘못된 부분이나 궁금하신 부분이 있으시면 댓글로 피드백 부탁드립니다.



감사합니다.




Comments