ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로그인 기능 구현(Spring + MyBatis + Spring Security)
    취업용 Spring 프로젝트/기능 내역 2019. 7. 17. 13:21

    기능 - 데이터베이스의 데이터와 사용자 입력 데이터를 비교해 같으면 main페이지로 이동

             - 권한 없는 사용자는 로그인 페이지 외 다른 페이지 접속 불가

     

    spring-security 라이브러리 추가

    <dependency>
    	<groupId>org.springframework.security</groupId>
    	<artifactId>spring-security-core</artifactId>
    	<version>4.2.3.RELEASE</version>
    </dependency>
    <dependency>
    	<groupId>org.springframework.security</groupId>
    	<artifactId>spring-security-web</artifactId>
    	<version>4.2.3.RELEASE</version>
    </dependency>
    <dependency>
    	<groupId>org.springframework.security</groupId>
    	<artifactId>spring-security-config</artifactId>
    	<version>4.2.3.RELEASE</version>
    </dependency>

     

    필터 추가 - web.xml

     

    *주의 - security 필터가 먼저 작동되면 encoding 필터가 작동하지 않는다. 

    <filter>
    	<filter-name>encoding</filter-name>
    	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    	<init-param>
    		<param-name>encoding</param-name>
    		<param-value>UTF-8</param-value>
    	</init-param>
    </filter>
    	
    <filter-mapping>
    	<filter-name>encoding</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>
    	
    <filter>
    	<filter-name>springSecurityFilterChain</filter-name>
    	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    	
    <filter-mapping>
    	<filter-name>springSecurityFilterChain</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>

     

    security 설정 파일 추가 - web.xml  

    <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>

     

    security-context.xml 작성

    <?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"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/security
    	http://www.springframework.org/schema/security/spring-security-4.2.xsd
    	http://www.springframework.org/schema/beans 
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context 
    	http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    	<context:component-scan base-package="com.management.dao">
    	</context:component-scan>
    		
    <security:http auto-config="true" use-expressions="true">
    	<security:intercept-url pattern="/loginPage" access="permitAll"/>
    	<security:intercept-url pattern="/resources/**/**" access="permitAll"/>
    	<security:intercept-url pattern="/**" access="hasAnyRole('USER, ADMIN')" />
    	<security:form-login
    		username-parameter ="id"
    		password-parameter="pw"
    		login-page="/loginPage"
    		default-target-url="/main"
    	/>
    </security:http>
            
        <security:authentication-manager>
    	<security:authentication-provider ref = "userAuthProvider"/>
            <security:authentication-provider user-service-ref="userService"/>
        </security:authentication-manager>
    
    	<bean id = "userService" class = "com.management.service.CustomUserDetailsService"></bean>
    	<bean id = "userAuthProvider" class = "com.management.service.CustomAuthenticationProvider"></bean>
    </beans>

     

    <security:http> </security:http> 요소에서 웹 관련 설정을 한다.

     

    <security:intercept-url pattern = "" access = "" /> 에서 접근 권한 설정

    *주의 - 정적 파일들이 들어있는 resources는 permitAll(모든 사용자 접근 허용) 적용 해줘야 css,js를 읽어올 수 있다.

               - 스프링 시큐리티는 권한이 없는 사용자가 페이지 접근 시 로그인 페이지로 redirect 시켜버리는데,

                 로그인 페이지에 permitAll을 적용하지 않으면 로그인 페이지에서도 권한을 요구하므로

                 로그인 페이지가 계속 redirect된다... (소중한 3시간을 날려버린 사소한 오류들)

     

    <security:form-login   /> 에서 스프링 시큐리티가 주는 default 로그인 페이지를 커스텀 할 수 있다.

    -username-parameter : input 아이디 파라미터명 변경 (default = username)

    -password-parameter : input 비밀번호 파라미터명 변경 (default = password)

    -login-page : 기본 스프링 시큐리티 페이지를 직접 만든 페이지로 변경

    -default-target-url : 로그인 성공 후 이동할 페이지 

     

    <security:authentication-manager> </security:authentication-manager> 에서 인증 관련 설정

    - 3가지 인터페이스를 이용해 스프링 시큐리티 인증을 구현한다.

     

    org.springframework.security.core.userdetails.UserDetails

    - 사용자 정보를 담음 (DTO 역할)

    org.springframework.security.core.userdetails.UserDetailsService

    - 데이터베이스에서 사용자 데이터를 가져옴

    org.springframework.security.core.authentication.AuthenticationProvider

    - UserDetailsService에서 가져온 데이터와 사용자 입력 데이터를 비교해 로그인 인증 처리 실행

     

     

    UserDetails

     

    @SuppressWarnings("serial")
    public class CustomUserDetails implements UserDetails{
    
    	private String id;
    	private String pw;
    	private String authority;
    	private boolean enabled;
    
    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
    			auth.add(new SimpleGrantedAuthority(authority));
    		return auth;
    	}
    
    	@Override
    	public String getPassword() {
    		return pw;
    	}
    
    	@Override
    	public String getUsername() {
    		return id;
    	}
    
    	@Override
    	public boolean isAccountNonExpired() {
    		return true;
    	}
    
    	@Override
    	public boolean isAccountNonLocked() {
    		return true;
    	}
    
    	@Override
    	public boolean isCredentialsNonExpired() {
    		return true;
    	}
    
    	@Override
    	public boolean isEnabled() {
    		return enabled;
    	}
    }

    UserDetails 인터페이스를 구현한 CustomUserDetails 클래스

     

     

    return 타입 메소드명 설명
    String getUsername() 계정의 이름을 리턴
    String getPassword() 계정의 패스워드를 리턴
    boolean isAccountNonExpired() 계정이 만료되지 않았는지를 리턴 (true = 만료되지 않음)
    boolean isAccountNonLocked() 계정이 잠겨있지 않았는지를 리턴 (true = 잠겨있지 않음)
    boolean isCredentialsNonExpired() 계정의 패스워드가 만료되지 않았는지를 리턴 (true = 패스워드가 만료되지 않음)
    boolean isEnabled() 계정이 사용가능한 계정인지를 리턴 (true = 사용가능한 계정)

    Collection<?  extends GrantedAuthority>

    getAuthorities() 계정이 갖고 있는 권한 목록을 리턴

    UserDetails 인터페이스의 메소드

     

     

     

    AuthenticationProvider

     

    public class CustomAuthenticationProvider implements AuthenticationProvider {
    
    	@Inject
    	private UserDetailsService service;
    	
    	@Override
    	public Authentication authenticate(Authentication authentication) 
        							throws AuthenticationException {
            
            	//사용자 입력 값
    		String id = (String)authentication.getPrincipal();
    		String pw = (String)authentication.getCredentials();
    		
    		CustomUserDetails user = (CustomUserDetails)service.loadUserByUsername(id);
    		
    		if(!matchPassword(pw, user.getPassword())) {
                	throw new BadCredentialsException(id);
           	 }
    
    
    		return new UsernamePasswordAuthenticationToken(id, pw, user.getAuthorities());
    	}
    
    
    	@Override
    	public boolean supports(Class<?> authentication) {
    		return true;
    	}
    	
    	private boolean matchPassword(String pw, String password) {
    		return pw.equals(password);
    	}
    }

    AuthenticationProvider 인터페이스를 구현한 CustomAuthenticationProvider

     

     

    UserDetailsService

     

    public class CustomUserDetailsService implements UserDetailsService{
    
    	@Inject
    	private MemberDAO dao;
    	
    	@Override
    	public UserDetails loadUserByUsername(String id) 
        						throws UsernameNotFoundException {
                                
    		CustomUserDetails user = dao.login(id);
    		
    		if(user == null) {
    			throw new UsernameNotFoundException(id);
    		}
    		return user;
    	}
    }

    UserDetailsService 인터페이스를 구현한 CustomUserDetailsService

     

     

    DAO

     

    @Override
    public CustomUserDetails login(String id) {
    	return sqlSession.selectOne(namespace + ".login", id);
    }

     

    Mapper

     

    <select id="login" resultType="CustomUserDetails">
        SELECT
        	*
        FROM
        	member
        WHERE
        	id = #{id}
    </select>

     

     

    동작

     

     

    로그인 성공

     

     

    로그인 실패

     

    권한 없는 사용자 접근 시 로그인 redirect

     

     

    -끝-

Designed by Tistory.