Framework/Spring Framework

(23.01.11.)Spring ํ”„๋ ˆ์ž„์›Œํฌ : Spring Security -JDBC Authentication

ํ”„๋กœ๊ทธ๋ž˜๋จธ ์˜ค์›” 2023. 1. 11.

Spring Security -JDBC Authentication

 

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๋™ํ•˜์—ฌ ์ธ๊ฐ€, ์ธ์ฆ ๋ณด์•ˆ์„ ํ•˜๋ ค๋ฉด 3๊ฐ€์ง€๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

-config  ํด๋ž˜์Šค์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ์žˆ์–ด์•ผํ•œ๋‹ค. (Datasource)

-users์™€ authorities ํ…Œ์ด๋ธ”์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

-๊ธฐ์กด์— ํ–ˆ๋˜ In Memory Authentication ์„ค์ • ์ œ๊ฑฐํ•ด์ค˜์•ผํ•œ๋‹ค. 

 

 

users์™€ authorities ํ…Œ์ด๋ธ” ์ƒ์„ฑ

 

CREATE TABLE users (
    username VARCHAR2(50) NOT NULL,
    password VARCHAR2(120) NOT NULL,
    enabled CHAR(1) NOT NULL,
    CONSTRAINT check_users_enabled CHECK(enabled IN('y','n')),
    CONSTRAINT pk_users_username PRIMARY KEY(username)
);

 

CREATE TABLE authorities (
    username VARCHAR2(50) NOT NULL,
    authority VARCHAR2(50) NOT NULL,
    CONSTRAINT fk_authorities_username FOREIGN KEY(username) REFERENCES users(username)
);

 

 

 

INSERT INTO users VALUES('smith', 'smith','y');
INSERT INTO authorities VALUES('smith', 'ROLE_ADMIN');

INSERT INTO users VALUES('blake', 'blake','y');
INSERT INTO authorities VALUES('blake', 'ROLE_USER');

INSERT INTO users VALUES('jones', 'jones','y');
INSERT INTO authorities VALUES('jones', 'ROLE_GUEST');

 

 

--'smith' ๋ฌธ์ž์—ด ์•”ํ˜ธํ™”
UPDATE users SET password='$2a$10$LWbsj6DG/JmWkz3uY9I89.xf2/9UpaWaMgLLKb1PMIkpxZwTACOqS' WHERE username='smith';

--'blake' ๋ฌธ์ž์—ด ์•”ํ˜ธํ™”
UPDATE users SET password='$2a$10$B3wLDsAfD3vpGv3tl8X/xe.8c7SCrHubEOqts0PA5e49cB5al3RP2' WHERE username='blake';

--'jones' ๋ฌธ์ž์—ด ์•”ํ˜ธํ™”
UPDATE users SET password='$2a$10$KIjMtKbXBRtL5kolqFGEIehF40IZa5kCkcrbZfNWHtRCmNF3/V7WO' WHERE username='jones';

 

๊ฒฐ๊ณผ : 

 

 

 

 

 


 

 

 

config  ํด๋ž˜์Šค์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ์žˆ์–ด์•ผํ•œ๋‹ค. (Datasource)  & ๊ธฐ์กด์— ํ–ˆ๋˜ In Memory Authentication ์„ค์ • ์ œ๊ฑฐํ•ด์ค˜์•ผํ•œ๋‹ค. 

 

SimpleSecurityConfig1.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
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
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
@Configuration
@EnableWebSecurity
public class SimpleSecurityConfig1
{
   @Autowired
    DataSource dataSource;    // JDBC Authentication์— ํ•„์š”ํ•จ
 
   @Bean
   public BCryptPasswordEncoder  passwordEncoder() {
      BCryptPasswordEncoder enc = new BCryptPasswordEncoder();
      log.info("smith->" + enc.encode("smith")); // ROLE_ADMIN
      log.info("blake->" + enc.encode("blake")); // ROLE_USER
      log.info("jones->" + enc.encode("jones")); // ROLE_GUEST
       return enc;
   }
   /*
    * smith->$2a$10$kqk98pSUNsPwdnzED3IC5./1Qw/MukfF33WhdQjRhvhzygmiISriu
    * blake->$2a$10$peWBE7KRBkxj3h9WHjiTQuplVc.homrK.MumRdV3gfc6hC4qShcYG
    * jones->$2a$10$x1bf..r/ZOewaq9xPLw2nuYRMFZwBbgHBAiEOvuNhQEYuIPzHz/SC
    */
   
   //Enable jdbc authentication
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
       log.info("๋ฐ์ดํ„ฐ์†Œ์Šค ์„ค์ •");
        auth.jdbcAuthentication().dataSource(dataSource);
    }
 
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
       return (web) -> web.ignoring().antMatchers("/resources/**""/ignore2");
    }
 
   @Bean
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      log.info("์ ‘๊ทผ์ œํ•œ ์„ค์ •");
      return http.authorizeHttpRequests()/* ๊ถŒํ•œ์— ๋”ฐ๋ฅธ ์ธ๊ฐ€(Authorization) */
            .antMatchers("/""/sec/""/sec/loginForm""/sec/denied""/logout").permitAll()
              .antMatchers("/sec/hello").hasAnyRole("USER""ADMIN")
               .antMatchers("/sec/getemps").hasAnyRole("USER""ADMIN")
               .antMatchers("/sec/addemp").hasAnyRole("ADMIN")
               .antMatchers("/sec/main").hasAnyRole("USER","GUEST","ADMIN")
               .antMatchers("/sec/sample").hasAnyRole("GUEST""ADMIN")
            //.anyRequest().authenticated()  // ์œ„์˜ ์„ค์ • ์ด์™ธ์˜ ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ์„ ๊ฑฐ์ณ์•ผ ํ•œ๋‹ค
               .anyRequest().permitAll()        // ์œ„์˜ ์„ค์ • ์ด์™ธ์˜ ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ ์š”๊ตฌํ•˜์ง€ ์•Š์Œ
               
            .and()
            .csrf().disable()    //csrf ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ
            //.csrf().ignoringAntMatchers("/logout") //์š”์ฒญ์‹œ 'POST' not supported ์—๋Ÿฌ ๋ฐฉ์ง€
            //.ignoringAntMatchers("/sec/loginForm")
            //.ignoringAntMatchers("/doLogin")
 
            .formLogin().loginPage("/sec/loginForm")   // ์ง€์ •๋œ ์œ„์น˜์— ๋กœ๊ทธ์ธ ํผ์ด ์ค€๋น„๋˜์–ด์•ผ ํ•จ
               .loginProcessingUrl("/doLogin")            // ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์†Œ๋“œ ๋ถˆํ•„์š”, ํผ action๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ
               .failureUrl("/sec/login-error")      // ๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํผ์œผ๋กœ
               //.failureForwardUrl("/login?error=Y")  //์‹คํŒจ์‹œ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ forward
               .defaultSuccessUrl("/sec/main"true)
               .usernameParameter("userid")  // ๋กœ๊ทธ์ธ ํผ์—์„œ ์ด์šฉ์ž ID ํ•„๋“œ ์ด๋ฆ„, ๋””ํดํŠธ๋Š” username
               .passwordParameter("userpwd")  // ๋กœ๊ทธ์ธ ํผ์—์„œ ์ด์šฉ์ž ์•”ํ˜ธ ํ•„ํŠธ ์ด๋ฆ„, ๋””ํดํŠธ๋Š” password
               .permitAll()
               
               .and()   // ๋””ํดํŠธ ๋กœ๊ทธ์•„์›ƒ URL = /logout
               .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")) //๋กœ๊ทธ์•„์›ƒ ์š”์ฒญ์‹œ URL
             .logoutSuccessUrl("/sec/loginForm?logout=T")
             .invalidateHttpSession(true
             .deleteCookies("JSESSIONID")
             .permitAll()
             
             .and()
             .exceptionHandling().accessDeniedPage("/sec/denied")
             .and().build();
   }
 
   /*  ์•„๋ž˜์˜ ๋‚ด์šฉ์€ In-Memory Authentication์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ JDBC Authentication์—๋Š” ๋ถˆํ•„์š”
   @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationMgr) throws Exception {
        authenticationMgr.inMemoryAuthentication().withUser("employee").password("$2a$10$MZ2ANCUXIj5mrAVbytojruvzrPv9B3v9CXh8qI9qP13kU8E.mq7yO")
            .authorities("ROLE_USER").and().withUser("imadmin").password("$2a$10$FA8kEOhdRwE7OOxnsJXx0uYQGKaS8nsHzOXuqYCFggtwOSGRCwbcK")
            .authorities("ROLE_USER", "ROLE_ADMIN").and().withUser("guest").password("$2a$10$ABxeHaOiDbdnLaWLPZuAVuPzU3rpZ30fl3IKfNXybkOG2uZM4fCPq")
            .authorities("ROLE_GUEST");
    }*/
}
cs

config  ํด๋ž˜์Šค์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ์žˆ์–ด์•ผํ•œ๋‹ค. (Datasource)    >  23 ํ–‰ ~ 45 ํ–‰

๊ธฐ์กด์— ํ–ˆ๋˜ In Memory Authentication ์„ค์ • ์ œ๊ฑฐํ•ด์ค˜์•ผํ•œ๋‹ค.   >  92ํ–‰  ~ 99ํ–‰

 

 

SecurityController.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
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
@RequestMapping("/sec")
public class SecurityController {
    @GetMapping("/")
 
    public String index() {
        return "thymeleaf/index";
    }
    @GetMapping("/loginForm")
    public String loginForm() {
        return "thymeleaf/loginForm";
    }
    @GetMapping("/main")
    public String goMain() {
        return "thymeleaf/main";
    }
    @GetMapping("/hello")
    @ResponseBody
    public String goHello() {
        return "๋‹น์‹ ์€ ์ด์šฉ์ž ๋˜๋Š” ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค.";
    }
 
    @GetMapping("/login-error")
    public String fail(Model model) {
        model.addAttribute("loginError",true);
        return "thymeleaf/loginForm";
    }
}
cs

 

 

 

index.html

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
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
     xmlns:sec="http://www.thymeleaf.org/extras/thymeleaf-extras-springsecurity5">
<head>
<meta charset="utf-8">
<title>INDEX</title>
</head>
<body>
<h2>Welcome</h2>
        <p>Spring Security Thymeleaf tutorial</p>
        <div sec:authorize="isAnonymous()">
           You should login in for using this.
           
        </div>
        <div sec:authorize="hasRole('USER')">
           Text visible to user.
        </div>
        <div sec:authorize="hasRole('ADMIN')">
           Text visible to admin.
        </div>
        <div sec:authorize="hasAnyRole('ADMIN','USER')">
           Text visible to admin or user.
        </div>
        <div sec:authorize="isAuthenticated()">
            <div>Authenticated username:
              <span sec:authentication="name"></span>
           </div>
           <div>Authenticated user roles:
              <span sec:authentication="principal.authorities"></span>
           </div>
        </div>
</body>
</html>
cs

 

 

 

loginForm.html

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”</title>
<style>
    h1{width:fit-content; margin:0 auto;}
   label{display:inline-block;width:3em; text-align: right; 
      margin-right:1em; }
   input { width: 5em; }
   form {width:fit-content; padding:0.5em; margin:0 auto; 
      border:1px solid black;}
   button { margin: 5px; }
   div:last-child { text-align: center;} 
   .error {color: red; text-align: center;}
</style>
</head>
<body>
<h1>๋กœ๊ทธ์ธ</h1>
<p th:if= "${loginError}" class="error">Wrong userid or password</p>
<form action="/doLogin" method="post">
   <div><label>์•„์ด๋””</label> 
      <input id="uid" type="text" name="userid" value="">
   </div>
   <div><label>์•” ํ˜ธ</label> 
      <input id="pwd" type="password" name="userpwd" value="">
   </div>
   <div><button type="submit">๋กœ๊ทธ์ธ</button></div>
</form>
</body>
</html>
cs

 

 

 

main.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org" 
   xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
   <head>
      <meta charset="utf-8">
      <title>Spring Boot Thymeleaf Test</title> 
     <!-- <link href="/css/common.css" rel="stylesheet" /> -->
   </head>
   <body>
      <!-- <th:block th:include="@{/thymeleaf/fragments/header}"></th:block>  -->
 
      <div class="content">
         <h2>This is Content</h2>
         <div th:each="item ,status: ${list}" th:text="${item}" 
            th:id="|id_${status.index}|"></div>
      </div> 
      <a th:href="${'/logout'}">๋กœ๊ทธ์•„์›ƒ</a>
     <!-- <th:block th:include="@{/thymeleaf/fragments/footer}"></th:block>  -->
   </body>
</html>
cs

 

 

์‹คํ–‰๊ฒฐ๊ณผ : 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

๋Œ“๊ธ€