본문 바로가기
Spring/study

spring 30강 Spring Security

by avvin 2019. 7. 16.

spring 30강 Spring Security


웹 보안의 3요소


1) 인증 (Authentication) : 해플리케이션의 작업을 수행할 수 있는 주체(사용자). 

     현재 접속중인 사용자가 누구인지 확인하는 과정.


2) 권한 인가(Authorization) : 인증된 주체가 애플리케이션의 동작을 수행할 수 있도록 허락되어있는지 증명하는 과정.

    현재 사용자가 특정 url에 접속할 권한이 잇는지 검사하는 과정


3) UI 처리 : 권한이 없는 사용자가 접근할 경우의 에러 화면 등을 보여주는 과정



스프링 시큐리티


개발자가 직접 처리하던 보안 처리 과정을 스프링 프레임워크에서 제공하는 스프링 시큐리티를 사용하여 

사용권한 관리, 비밀번호 암호화, 회원가입 처리, 로그인, 로그아웃 등의 웹 보안 관련 기능 개발을 쉽게 처리할 수 있음




1. 회원 정보 테이블 생성


스프링시큐리티.sql

create table users(


userid varchar2(255) not null, 


passwd varchar2(255) not null, --비밀번호 (암호화 처리)


name varchar2(255) not null, 


enabled number(1) default 1, --계정 사용 가능 여부 (휴면 계정일 경우 0)


authority varchar2(20) default 'ROLE_USER', --일반사용자/관리자


primary key(userid)


);


select * from tab;



Spring Legacy Project MVC Pattern

spring06_security

com.example.security


pom.xml 설정하기


java 버전 1.8

<spring.security>

1
2
3
4
5
6
7
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.3.RELEASE</org.springframework-version>
        <org.aspectj-version>1.8.10</org.aspectj-version>
        <org.slf4j-version>1.7.25</org.slf4j-version>
        <spring.security.version>3.2.10.RELEASE</spring.security.version>
    </properties>



오라클 라이브러리 때문에 저장소 추가

1
2
3
4
5
6
<repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>



오라클 드라이버 메이븐 설정하기


오라클 드라이버 메이븐 설정시 repository를 별도로 지정해주어야 한다.
지정해주지 않으면 Missing artifact ... 오류 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
<repositories>
        <!-- ojdbc14 설정시 --> 
 <repository>
  <id>mesir-repo</id>
  <url>http://mesir.googlecode.com/svn/trunk/mavenrepo</url>
 </repository>
        <!-- ojdbc6 설정시 --> 
 <repository>
      <id>codelds</id>
      <url>https://code.lds.org/nexus/content/groups/main-repo</url>
    </repository>
</repositories>
 


출처: https://appsnuri.tistory.com/112 [이야기앱 세상]


Spring 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
<!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring.security.version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${spring.security.version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>



나머지는 샘플프로젝트와 동일



설정이 까다로움


WEB-INF/web.xml

1
2
3
4
5
6
7
8
9
    <!-- The definition of the Root Spring Container shared by all Servlets 
        and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/root-context.xml
            /WEB-INF/spring/security/security-context.xml //추가
        </param-value>
    </context-param>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    <!-- 스프링 시큐리티에서 사용하는 
    세션 이벤트 처리 관련 리스터 세션 만료 체크
    동시 로그인 제한 등의 기능 제공 -->
    <listener>
        <listener-class>
            org.springframework.security.web.session.HttpSessionEventPublisher
        </listener-class>
    </listener>
 
    <!-- 애플리케이션의 모든 요청을 
    스프링 시큐리티에서 처리하도록 하는 필터 -->
    <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>




servlet-context.xml


namespace - 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
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:security="http://www.springframework.org/schema/security" //namespace 설정하면 추가되는 코드
    xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security // 오류뜨면 버전정보를 지워본다 : security-5.1.xsd
http://www.springframework.org/schema/security/spring-security.xsd">//namespace 설정하면 추가되는 코드
    <!-- DispatcherServlet Context: defines this servlet's request-processing 
        infrastructure -->
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />
    <!-- Handles HTTP GET requests for /resources/** by efficiently serving 
        up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />
    <resources location="/WEB-INF/views/include/" mapping="/include/**" />
    <!-- Resolves views selected for rendering by @Controllers to .jsp resources 
        in the /WEB-INF/views directory -->
    <beans:bean // view resolver
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    <context:component-scan base-package="com.example.security" />
    <!-- @Secured 어노테이션 활성화, 사용 권한 제한, 메서드에 @Secured 붙이면 스프링시큐리티 적용받는다.-->
    <security:global-method-security
        secured-annotations="enabled" />
</beans:beans>
cs



WEB-INF/spring/security/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
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
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans //이 부분이 스프링 시큐리티 설정파일임을 알려주는것같은데 설명이 생략돼있음
    xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation=
    "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
    
    <!-- 정적 리소스 파일들에는 보안 설정을 하지 않음 -->
    <http pattern="/include/**" security="none" />
    <http auto-config="true" use-expressions="true" 
        create-session="never">
        
        <!-- 관리자 영역 설정 -->
        <intercept-url pattern="/admin/**" //관리자만 이 url로 접속할 수 있도록 설정
            access="hasRole('ROLE_ADMIN')" />
            
        <!-- 권한에 관계없이 접속 가능한 영역(guest도 접속 가능) -->
        <intercept-url pattern="/user/**" access="permitAll" />
        
        <!-- 로그인한 사용자 영역 -->
        <intercept-url pattern="/**"
        access= //로그인한 사용자는 모두 접근 가능한 영역 /** (GUEST도 로그인한 사용자..?)
        "hasAnyRole('ROLE_USER','ROLE_TEST','ROLE_ADMIN','ROLE_GUEST')" />
        
        <!-- 로그인폼 -->
        <form-login login-page="/user/login.do"
            login-processing-url="/user/login_check.do"
            authentication-success-handler-ref=
                "userLoginSuccessHandler"
            authentication-failure-handler-ref=
                "userLoginFailureHandler"
            username-parameter="userid" 
                password-parameter="passwd" />
        <session-management>
        
            <!-- max-sessions="1" 동시접속 막기 
                 error-if-maximum-exceeded="true" 로그인 세션 
                     초과시 에러 옵션 expired-url="/user/login" 
                  세션 만료시 이동할 주소 --> //세션 만료시간은 web.xml에 설정
            <concurrency-control max-sessions="1"
                expired-url="/user/login.do" 
                error-if-maximum-exceeded="true" /> //설정한 맥시멈을 초과하면 에러메시지 띄움
        </session-management>
        
        <!-- 로그아웃 관련 처리 -->
        <logout delete-cookies=
        "JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"
            logout-success-url="/user/login.do" 
            logout-url="/user/logout.do"
            invalidate-session="true" /
            
        <!-- 접근권한이 없는 경우의 코드 -->
        <access-denied-handler ref="userDeniedHandler" />
        
        <!-- 자동 로그인 관련 쿠키 저장, 
        세션이 종료된 후에도 자동 로그인할 수 있는 기능 
        86400 1일, 604800 7일 -->
        <remember-me key="userKey" 
        token-validity-seconds="86400" />
    </http>
    <beans:bean id="userDeniedHandler"
        class="com.example.security.service.UserDeniedHandler" />
    <beans:bean id="userLoginSuccessHandler"
        class=
"com.example.security.service.UserLoginSuccessHandler" />
    <beans:bean id="userLoginFailureHandler"
        class=
"com.example.security.service.UserLoginFailureHandler" />
 
    <!-- 로그인 인증을 처리하는 빈 -->
    <beans:bean id="userService" 
        class=
"com.example.security.service.UserAuthenticationService">
        <beans:constructor-arg name="sqlSession" 
        ref="sqlSession" />
    </beans:bean>
    
    <!-- 사용자가 입력한 비밀번호를 암호화된 
    비밀번호와 체크하는 로직이 포함됨 -->
    <authentication-manager>
        <authentication-provider user-service-ref="userService">
            <password-encoder ref="passwordEncoder">
                <salt-source user-property="username" />
            </password-encoder>
        </authentication-provider>
    </authentication-manager>
    
    <!-- 비밀번호 암호화 빈 -->
    <beans:bean id="passwordEncoder"
        class=
"org.springframework.security.authentication.encoding.ShaPasswordEncoder">
        <beans:constructor-arg name="strength" value="256" />
    </beans:bean>
</beans:beans>



로그인 폼 페이지,로그인 프로세스 url 설정 / 로그인 인증 처리하는 빈 설정하면


로그인 프로세스 url와 연결될 때 설정해둔 로그인 인증처리 클래스 (UserAthenticationService.jave)로 넘어간다.

( 정확히는 UserAthenticationService 클래스에서 오버라이딩된 

  public UserDetails loadUserByUsername(String userid) 메서드가 실행되는것 //userid는 로그인 form에서 submit된 id

  스프링 시큐리티 내부에서 일어나는 작업이므로 개발자가 신경쓸 필요는 없고 그냥 그런가보다하고 넘어가면 될거같다.)


이러한 요청들을 스프링 시큐리티에서 처리하도록 하는 설정은 web.xml에서 했다

    <!-- 애플리케이션의 모든 요청을 
    스프링 시큐리티에서 처리하도록 하는 필터 -->
    <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>


DelegationFilerProxy 라는 스프링 내장 클래스가 모든요청(/*)에 대하여 스프링 시큐리티가 처리할 수 있도록 한다 

(스프링 시스템 내부적으로 일어나는 작업이므로 상세히 알긴 힘들다.)


root-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
<?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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <!-- Root Context: defines shared resources visible to all other web components -->
    <bean id="dataSource"
        class=
"org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 드라이버 클래스 이름이 변경됨 -->
        <property name="driverClassName" 
        value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
        <!-- 연결문자열에 log4jdbc가 추가됨 -->
        <property name="url" value=
        "jdbc:log4jdbc:oracle:thin:@localhost:1521/xe" />
        <property name="username" value="spring" />
        <property name="password" value="123456" />
    </bean>
    <!-- SqlSessionFactory 객체 주입 -->
    <bean id="sqlSessionFactory" class=
    "org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" 
        value="classpath:/mybatis-config.xml"></property>
        <property name="mapperLocations" 
        value="classpath:mappers/**/*Mapper.xml"></property>
    </bean>
    <!-- SqlSession 객체 주입 -->
    <bean id="sqlSession" class=
    "org.mybatis.spring.SqlSessionTemplate"
        destroy-method="clearCache">
        <constructor-arg name="sqlSessionFactory" 
        ref="sqlSessionFactory"></constructor-arg>
    </bean>
    <tx:annotation-driven transaction-manager=
    "transactionManager" />
    <bean id="transactionManager"
        class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>



트랜젝션을 위한 코드 추가, namespace에서 tx 




src/main/resources/

mybatis-config.xml (sample) 추가 

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 
    <typeAliases>
    </typeAliases>
 
</configuration>



log4jdbc.log4j2.properties (기본 file로 생성하면 됨) 추가 

1
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
cs


logback.xml (sample) 추가 



실습 )  회원가입 / 로그인 / 로그아웃



UserDTO (★★★)

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
package com.example.security.model.dto;
 
import java.util.Collection;
 
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
 
public class UserDTO extends User { //스프링 시큐리티 내장객체 
                                    //사용자 상세정보를 담음
    
    private String userid// userid 변수 선언
    
    //UserDTO의 생성자 ( 자동생성 기능 사용 )
    //자동생성한 생성자 파라미터에 userid 추가
    public UserDTO(String username, String password, 
            boolean enabled, boolean accountNonExpired,
            boolean credentialsNonExpired, 
            boolean accountNonLocked, 
            Collection<extends GrantedAuthority> authorities,
            String userid) { //파라미터에 userid 추가
        super(username, password, enabled, accountNonExpired
                , credentialsNonExpired, accountNonLocked
                , authorities);
        
        this.userid = userid;
 
    }
    
    //getter/setter/toString
    
    public String getUserid() {
        return userid;
    }
 
    public void setUserid(String userid) {
        this.userid = userid;
    }
 
    @Override
    public String toString() {
        return "UserDTO [userid=" + userid + "]";
    }
 
}




UserDAO.java ( Interface )

1
2
3
4
5
6
7
8
9
10
package com.example.security.model.dao;
 
import java.util.Map;
 
public interface UserDAO {
    //회원가입 처리
    public int insertUser(Map<String,String> map);
    //로그인 처리 (회원 상세 정보)
    public Map<String,Object> selectUser(String userid);
}


UserDAOImpl.java 

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
package com.example.security.model.dao;
 
import java.util.Map;
 
import javax.inject.Inject;
 
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
@Repository
public class UserDAOImpl implements UserDAO {
    @Inject
    SqlSession sqlSession;
    
    @Override
    public int insertUser(Map<StringString> map) {
        return sqlSession.insert("user.insertUser", map);
    }
 
    @Override
    public Map<String, Object> selectUser(String userid) {
        return sqlSession.selectOne("user.selectUser", userid);
    }
 
}
 



src/main/resources/

userMapper.xml 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace="user">
 
    <insert id="insertUser">
        insert into users
        values (#{userid}, #{passwd}, #{name}, 1, #{authority})
    </insert>
    
    <select id="selectUser" resultType="java.util.Map"> //(★★★)
        select userid as usernamepasswd as password,//암호화된 비밀번호
            name, enabled, authority
        from users
        where userid=#{userid}
    </select>
    
</mapper>



username과 password는 스프링 시큐리티 기본 이름

아이디만 받아와서 대조


resultType을 Map으로 설정해서 dao에서 map에 데이터를 따로 담아 리턴할 필요가 없다



security-context에 설정한 security 처리 클래스 ( 모두 Service에서 처리 / DB 관련 처리만 DAO에서)


UserAthenticationService.java : 로그인 인증 처리

UserDeniedHandler.java : 관리자 페이지에 일반 사용자나 권한이 없는 사용자가 접근했을 때 에러메시지를 출력

UserLoginFailure.java : 로그인 실패시 메시지를 전달 하고 로그인 페이지로 포워드

UserLoginSuccess.java : 로그인 성공시 환영 메시지 전달 적절한 페이지로 포워드




UserAthenticationService.java // UserDetailsService 구현  

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
package com.example.security.service;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
 
import com.example.security.model.dto.UserDTO;
//로그인 처리 클래스
public class UserAuthenticationService 
    implements UserDetailsService { //스프링 시큐리티 내장클래스 구현 
 
    private SqlSessionTemplate sqlSession;
    public UserAuthenticationService() {     }
    public UserAuthenticationService(
            SqlSessionTemplate sqlSession) {
        this.sqlSession=sqlSession;
    }
    
    //비밀번호 체크는 코드로 보이지 않아도 
    //이 메서드가 실행될 때 스프링 시큐리티에서 알아서 처리함
    @Override
    public UserDetails loadUserByUsername(String userid) 
            throws UsernameNotFoundException {
        //사용자 아이디 확인
        Map<String,Object> user=sqlSession.selectOne(
                "user.selectUser", userid);
        
        //아이디가 없으면 예외 발생시킴 (throw)
        if(user==null
            throw new UsernameNotFoundException(userid);
    
        //사용권한 목록
        List<GrantedAuthority> authority=new ArrayList<>();
        
        //Oracle DB의 경우 자바코드에서 테이블 필드명을 쓸 땐 대문자로 써야함
        //case 신경안쓰면 에러남
        authority.add(new SimpleGrantedAuthority(
                user.get("AUTHORITY").toString())); //필드명은 대문자로
        
        return new UserDTO(user.get("USERNAME").toString(),
                user.get("PASSWORD").toString(),
                (Integer)Integer.valueOf(user.get("ENABLED").toString())==1,
                true,true,true,authority,
                user.get("USERNAME").toString());
    }
 
}
 



UserDeniedHandler.java // AccessDeniedHandler 구현  

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
package com.example.security.service;
 
import java.io.IOException;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
 
public class UserDeniedHandler 
    implements AccessDeniedHandler {
 
//사용권한이 없을 때 지정한 페이지로 이동
    @Override
    public void handle(HttpServletRequest req
            , HttpServletResponse res, AccessDeniedException ade)
            throws IOException, ServletException { 
        
        req.setAttribute("errMsg""관리자만 사용할 수 있는 기능입니다.");
        
        String url="/WEB-INF/views/user/denied.jsp";
        
        RequestDispatcher rd=req.getRequestDispatcher(url);
        
        rd.forward(req, res);
        
        //한줄로
        //req.getRequestDispatcher("/WEB-INF/views/user/denied.jsp").forward(req, res);
    }
 
}




UserLoginFailure.java // AuthenticationFailureHandler 구현

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
package com.example.security.service;
 
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.AuthenticationFailureHandler;
 
public class UserLoginFailureHandler 
    implements AuthenticationFailureHandler {
//로그인이 실패했을 때의 처리
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request, 
            HttpServletResponse response,
            AuthenticationException exception) 
                    throws IOException, ServletException {
        
        //request 영역에 변수 저장
        request.setAttribute("errMsg",
                "아이디 또는 비밀번호가 일치하지 않습니다.");
        //forward
        request.getRequestDispatcher(
                "/WEB-INF/views/user/login.jsp")
                    .forward(request, response);
    }
}



UserLoginSuccess.java // AuthenticationSuccessHandler 구현

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
package com.example.security.service;
 
import java.io.IOException;
 
import javax.servlet.RequestDispatcher;
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.AuthenticationSuccessHandler;
 
import com.example.security.model.dto.UserDTO;
 
public class UserLoginSuccessHandler 
    implements AuthenticationSuccessHandler {
//로그인 처리가 성공했을 때의 코드
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, 
            HttpServletResponse response,
            Authentication auth) 
                    throws IOException, ServletException {
        
        //인증된 사용자의 정보 리턴
        UserDTO dto=(UserDTO)auth.getPrincipal();
        String msg=auth.getName()+"님 환영합니다.";
        request.setAttribute("msg", msg); //request 영역에 저장
        // 시작페이지로 포워딩
        RequestDispatcher rd=request.getRequestDispatcher("/");
        rd.forward(request, response); 
        //response.sendRedirect(request.getContextPath()+ "/");
 
    }
 
}




HomeController.java의 home메서드 ("/"로 RequestMapping되어있어 UserController와 충돌하므로)를 주석처리 


//비밀번호 암호화는 Controller에서 처리

UserController.java

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
package com.example.security.controller;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.inject.Inject;
 
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
 
import com.example.security.model.dao.UserDAO;
import com.example.security.service.ShaEncoder;
 
@Controller //컨트롤러 빈
public class UserController {
 
    @Inject
    ShaEncoder shaEncoder; //암호화 빈
    
    @Inject
    UserDAO userDao; 
    
    @RequestMapping("/"//시작 페이지
    public String home(Model model) {
        return "home"//home.jsp로 이동
    }
    //로그인 페이지로 이동
    @RequestMapping("/user/login.do")
    public String login() {
        return "user/login";
    }
    //회원가입 페이지로 이동
    @RequestMapping("/user/join.do")
    public String join() {
        return "user/join";
    }
//회원가입 처리     
    @RequestMapping("/user/insertUser.do")
    public String insertUser(@RequestParam String userid, 
            @RequestParam String passwd,
            @RequestParam String name, 
            @RequestParam String authority) {
        //비밀번호 암호화
        String dbpw=shaEncoder.saltEncoding(passwd, userid);
        Map<String,String> map=new HashMap<>();
        map.put("userid", userid);
        map.put("passwd", dbpw);
        map.put("name", name);
        map.put("authority", authority);
        // affected rows, 영향을 받은 행의 수가 리턴됨
        int result=userDao.insertUser(map);
        return "user/login"// login.jsp로 이동
    }
//관리자 영역 페이지    
    @RequestMapping("/admin/")
    public String admin() {
        return "admin/main";
    }
}



강의에서는 BCryptPasswordEncoder ( 비밀번호 암호화 객체 ) 사용



views/include/css/main.css

1
2
3
4
5
@charset "UTF-8";
a:link {text-decoration: none; color:blue; }
a:hover {text-decoration: underline; color: red;}
a:visited {text-decoration: none; }
a:active {text-decoration: none; color:yellow; }



views/include/header.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!-- 태그 라이브러리 선언 -->
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt"
uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" 
uri="http://java.sun.com/jsp/jstl/functions" %>
<!-- 컨텍스트 패스 설정 -->
<c:set var="path" value="${pageContext.request.contextPath}"/>
<!-- js, css 연결 -->
<script src="http://code.jquery.com/jquery-3.2.1.min.js">
</script>
<link rel="stylesheet" href="${path}/include/css/main.css">




views/user/login.jsp

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
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<%@ include file="../include/header.jsp" %>
 
<script type="text/javascript">
function join(){
    location.href="${path}/user/join.do";
}
</script>
 
</head>
<body>
<h2>로그인 페이지</h2>
<span style="color:red;">${errMsg}</span>
<form action="${path}/user/login_check.do" method="post">
<table>
    <tr>
        <td>아이디</td>
        <td><input name="userid"></td>
    </tr>
    <tr>
        <td>비밀번호</td>
        <td><input type="password" name="passwd">
            <input name="_spring_security_remember_me"
                type="checkbox">자동 로그인</td>
    </tr>
    <tr>
        <td colspan="2" align="center">
            <input type="submit" value="로그인">
            <input type="button" value="회원가입" onclick="join()">
        </td>
    </tr>
</table>
</form>
</body>
</html>



인강에 추가된 내용

<input type="hidden" name="${_csrf.parameterName}" value="{_csrf.token}">

스프링 시큐리티 csrf(크로스도메인) 설정 방법 : https://rockdrumy.tistory.com/1341 참고


_csrf 는 스프링 시큐리티 내장객체, 빠지면 에러남


Controller에는 login_check.do가 없다


security-context.xml에 설정돼있음

1
2
3
4
5
6
7
8
9
10
<!-- 로그인폼 -->
        <form-login login-page="/user/login.do"
            login-processing-url="/user/login_check.do"
            authentication-success-handler-ref=
                "userLoginSuccessHandler"
            authentication-failure-handler-ref=
                "userLoginFailureHandler"
            username-parameter="userid" 
                password-parameter="passwd" />
        <session-management>




spring 30강 Spring Security : https://www.youtube.com/watch?v=GWeohyzm9bA