Spring Security + H2

Spring Security + H2

참고 사이트

https://blog.outsider.ne.kr/904

https://velog.io/@ssseungzz7/Java-Exception-handling

https://github.com/kdevkr/spring-demo-security

Spring Security와 h2-console 함께 쓰기(★★★★★) - https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Security%EC%99%80%20h2-console%20%ED%95%A8%EA%BB%98%20%EC%93%B0%EA%B8%B0.md

H2 에러 : https://sosohanya.tistory.com/27

New Password Storage In Spring Security 5 : https://www.baeldung.com/spring-security-5-password-storage

Default Password Encoder in Spring Security 5 : https://www.baeldung.com/spring-security-5-default-password-encoder

Registration with Spring Security – Password Encoding : https://www.baeldung.com/spring-security-registration-password-encoding-bcrypt

스프링 시큐리티(★★★★★) : https://github.com/kdevkr/spring-demo-security/tree/4.x

[스프링프레임워크] 스프링 시큐리티 -2.사용자 상세 서비스 선택 : https://m.blog.naver.com/kimnx9006/220634017538

Spring Security Custom UserDetailsService(DB) 구현하기 : https://fntg.tistory.com/189

X-Frame-Options in Spring Security 문제)

대응 : http.headers().frameOptions().sameOrigin(); : https://stackoverflow.com/questions/40165915/why-does-the-h2-console-in-spring-boot-show-a-blank-screen-after-logging-in

@EnableWebSecurity : https://m.blog.naver.com/kimnx9006/220633299198

@Configuration : http://tech.javacafe.io/spring/2018/11/04/%EC%8A%A4%ED%94%84%EB%A7%81-Configuration-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%98%88%EC%A0%9C/

PasswordEncoder : https://stackoverflow.com/questions/49582971/encoded-password-does-not-look-like-bcrypt

PasswordEncoder : https://logical-code.tistory.com/106

Bcrypt 코딩시 주의사항 : http://yoonbumtae.com/?p=1202

Bcrypt 간단 설명 : https://www.joinc.co.kr/w/man/12/bcrypt

Bcrypt Password Generator : https://www.browserling.com/tools/bcrypt

외부 H2 이용 : https://www.baeldung.com/spring-boot-h2-database

H2 속성 : https://github.com/raycon/til/blob/master/framework/spring-boot-H2-database.md

H2 사용법 : https://developerhive.tistory.com/34

시큐리티 설정법 : https://sjh836.tistory.com/165

test가 있으면 제거해준다.

org.springframework.security

spring-security-test

package com.example.demo.library.Security ;

import org.springframework.beans.factory.annotation. Autowired ;

import org.springframework.context.annotation. Bean ;

import org.springframework.context.annotation. Configuration ;

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.configuration. EnableWebSecurity ;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter ;

import org.springframework.security.core.userdetails.User ;

import org.springframework.security.core.userdetails.UserDetails ;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder ;

import org.springframework.security.crypto.password.PasswordEncoder ;

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry ;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer ;

/**

* @Configuration

* 스프링의 @Configuration 어노테이션은 어노테이션기반 환경구성을 돕는다 .

* 이 어노테이션을 구현함으로써 클래스가 하나 이상의 @Bean 메소드를 제공하고

* 스프링 컨테이가 Bean 정의를 생성하고 런타임시 그 Bean 들이 요청들을 처리할 것을 선언하게 된다 .

*

* @EnableWebSecurity 애너테이션은 웹 보안을 활성화 한다 .

* 하지만 그자체로는 유용하지 않고 , 스프링 시큐리티가 WebSecurityConfigurer 를 구현하거나

* 컨텍스트의 WebSebSecurityConfigurerAdapter 를 확장한 빈으로 설정되어 있어야 한다 .

*/

@Configuration

@EnableWebSecurity

public class LibrarySecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {

// WebSecurityConfigurerAdapter 가 없을 때 기본 보안 설정을 한다 . 하나 이상 발견되면 설정된 보안 설정을 사용한다 .

@Autowired

private PasswordEncoder passwordEncoder ;

public LibrarySecurityConfig () {

super ( true ) ; // 기본 보안 구성 비활성화 , 공부하기위해 일부러 생성 원래는 없어도됨

}

@Override

public void addViewControllers (ViewControllerRegistry registry) {

registry.addViewController( "/" ).setViewName( "index" ) ;

registry.addViewController( "/login.html" ).setViewName( "login" ) ; // 매핑

}

@Override

protected void configure (HttpSecurity http) throws Exception {

http.securityContext()

.and().exceptionHandling()

// 예외 핸들링

.and().servletApi()

// 서블릿 API 통합

.and().httpBasic()

// http 기본인증

.and().logout().logoutSuccessUrl( "/" )

.and().headers()

// 로그아웃후 브라우저의 뒤로 가기 버튼을 사용하면 로그아웃이 성공하더라도 이전 페이지를 계속볼수 있다 .

// 이는 브라우적 ㅏ페이지를 캐시한다는 사실과 관련이 있다 .

// 그러므로 header() 구성 메소드로 보안 헤더를 활성화하면 브라우저가 페이지를 캐시하지 않도록 지시한다 .

// no-cache 헤더 옆에서 콘텐츠 스니핑이 비활성화되고 x-frame 보호가 활성화된다 .

.and().csrf()

.and().anonymous().principal( "guest" ).authorities( "ROLE_GUEST" )

.and().rememberMe()

// 기본적으로 사용자 이름 , 암호 , remember-me 만료 시간 및 개인 키를 토큰으로 인코딩하고 쿠키로 사용자의 브라우저에 저장한다 .

// 다음에 사용자가 동일한 웹 애플리케이션에 액세스하면 이 토큰이 감지돼 사용자가 잗종으로 로그인할 수 있따 .

// 정적 Remeber-Me 토큰은 해커가 캡쳐할 수 있기 때문에 보안문제가 있다 . 그러므로 롤링토큰을 지원하지만 ,

// 토큰을 유지하려면 데이터베이스가 필요하다 . 자세한것은 다음게시물에 작성예정이다 .

.and().formLogin().loginPage( "/login.html" ).defaultSuccessUrl( "/books.html" ).failureUrl( "/login.html?error=true" ).permitAll()

// 폼기반 로그인

.and().authorizeRequests()

.mvcMatchers( "/" ).permitAll()

// "/" 들어오는 요청은 모두허용

.anyRequest().authenticated() ;

// 어떤요청이든 인증확인

}

// 인메모리 기반 사용자 인증

/*

스프링 보안에서 인증은 체인으로 연결된 하나 이상의 AuthenticationProvider 에 의해 수행된다 .

이중 하나가 사용자를 성공적으로 인증하면 해당 사용자는 애플리케이션에 로그인할 수 있다 .

어느 인증 제공자라도 사용자가 비활성화됐거나 잠겼거나 자격 증명이 잘못됐다고 기록되거나 인증 제공자가

사용자를 인증할 수 없는 경우에는 사용자는 애플리케이션에 로그인할 수 없다 .

스프링 보안은 암호 (BCrypt 와 SCrypt 포함 ) 를 암호화하는 여러 가지 알고리즘을 지원하며 이러한 알고리즘을 위한 기본 암호 인코더를 제공

*/

@Override

protected void configure (AuthenticationManagerBuilder auth) throws Exception {

//PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

UserDetails adminUser = User. builder ()

.username( "admin@books.io" )

.password( passwordEncoder .encode( "secret" ))

.authorities( "ADMIN" , "USER" ).build() ;

UserDetails normalUser = User. builder ()

.username( "marten@books.io" )

.password( passwordEncoder .encode( "user" ))

.authorities( "USER" ).build() ;

UserDetails disabledUser = User. builder ()

.username( "jdoe@books.net" )

.password( passwordEncoder .encode( "user" ))

.disabled( true )

.authorities( "USER" ).build() ;

auth.inMemoryAuthentication()

.withUser(adminUser)

.withUser(normalUser)

.withUser(disabledUser) ;

}

@Bean

public PasswordEncoder passwordEncoder () {

return new BCryptPasswordEncoder() ;

}

}

index.html

Spring Boot Recipes - Library

Library

List of books

Login

login.html

Login

href ="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" >

body {

background-color : #DADADA ;

}

body > . grid {

height : 100 % ;

}

. column {

max-width : 450 px ;

}

Log-in to your account

th :name ="${_csrf.parameterName}" th :value ="${_csrf.token}" />

Login

Authentication Failed

Reason : Exception Here

list.html

Title

href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css">

body {

background-color: #DADADA;

}

body > .grid {

height: 100%;

}

.column {

max-width: 450px;

}

Log-out

Logut

결과)

H2 데이터베이스 이용하기

com.h2database

h2

runtime

org.springframework.boot

spring-boot-starter-jdbc

H2 다운로드 주소 : http://www.h2database.com/html/download.html

resources 밑에 schema.sql 파일을 만든다. 스프링 부트가 실행되면 자동으로 읽어서 실행한다.

drop table if exists USERS ;

drop table if exists AUTHORITIES ;

CREATE TABLE USERS (

ID BIGINT AUTO_INCREMENT,

USERNAME VARCHAR ( 50 ) NOT NULL,

PASSWORD VARCHAR ( 50 ) NOT NULL,

ENABLED SMALLINT NOT NULL,

PRIMARY KEY ( USERNAME )

) ;

CREATE TABLE AUTHORITIES (

ID BIGINT NOT NULL,

USERNAME VARCHAR ( 50 ) NOT NULL,

AUTHORITY VARCHAR ( 50 ) NOT NULL,

FOREIGN KEY ( USERNAME ) REFERENCES USERS( USERNAME )

) ;

INSERT INTO USERS ( USERNAME , PASSWORD , ENABLED ) VALUES

( 'admin@books.io' , '{noop}secret' ,true ) ,

( 'marten@books.io' , '{noop}user' ,true ) ,

( 'jdoe@books.net' , '{noop}user' ,false ) ;

-- {noop} 은 저장된 암호에 암호화가 적용되지 않았음을 나타낸다 .

-- 스프링 보안은 위임을 사용해 사용할 인코딩 방법을 결정한다 .

-- 값은 {bcrypt}, {scrypt}, {pdkdf2}, {sha256} 이 될수 있다 .

-- {sha256} 은 주로 호한성을 이유로 존재하며 비보안으로 간주해야 한다 .

INSERT INTO AUTHORITIES ( ID , USERNAME , AUTHORITY ) VALUES

( 1 , 'admin@books.io' , 'ADMIN' ) ,

( 1 , 'admin@books.io' , 'USER' ) ,

( 2 , 'marten@books.io' , 'USER' ) ,

( 3 , 'jdoe@books.net' , 'USER' ) ;

package com.example.demo.library.Security ;

import org.springframework.beans.factory.annotation. Autowired ;

import org.springframework.context.annotation. Configuration ;

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.configuration. EnableWebSecurity ;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter ;

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry ;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer ;

import javax.sql.DataSource ;

/**

* @Configuration

* 스프링의 @Configuration 어노테이션은 어노테이션기반 환경구성을 돕는다 .

* 이 어노테이션을 구현함으로써 클래스가 하나 이상의 @Bean 메소드를 제공하고

* 스프링 컨테이가 Bean 정의를 생성하고 런타임시 그 Bean 들이 요청들을 처리할 것을 선언하게 된다 .

*

* @EnableWebSecurity 애너테이션은 웹 보안을 활성화 한다 .

* 하지만 그자체로는 유용하지 않고 , 스프링 시큐리티가 WebSecurityConfigurer 를 구현하거나

* 컨텍스트의 WebSebSecurityConfigurerAdapter 를 확장한 빈으로 설정되어 있어야 한다 .

*/

@Configuration

@EnableWebSecurity

public class LibrarySecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {

// WebSecurityConfigurerAdapter 가 없을 때 기본 보안 설정을 한다 . 하나 이상 발견되면 설정된 보안 설정을 사용한다 .

@Autowired

private DataSource dataSource ;

public LibrarySecurityConfig () {

super ( true ) ; // 기본 보안 구성 비활성화 , 공부하기위해 일부러 생성 원래는 없어도됨

}

@Override

public void addViewControllers (ViewControllerRegistry registry) {

registry.addViewController( "/" ).setViewName( "index" ) ;

registry.addViewController( "/login.html" ).setViewName( "login" ) ; // 매핑

}

@Override

protected void configure (HttpSecurity http) throws Exception {

http.securityContext()

.and().exceptionHandling()

// 예외 핸들링

.and().servletApi()

// 서블릿 API 통합

.and().httpBasic()

// http 기본인증

.and().logout().logoutSuccessUrl( "/" )

.and().headers().frameOptions().sameOrigin()

// 로그아웃후 브라우저의 뒤로 가기 버튼을 사용하면 로그아웃이 성공하더라도 이전 페이지를 계속볼수 있다 .

// 이는 브라우저 페이지를 캐시한다는 사실과 관련이 있다 .

// 그러므로 header() 구성 메소드로 보안 헤더를 활성화하면 브라우저가 페이지를 캐시하지 않도록 지시한다 .

// no-cache 헤더 옆에서 콘텐츠 스니핑이 비활성화되고 x-frame 보호가 활성화된다 .

.and().csrf().ignoringAntMatchers( "/h2-console/**" )

.and().anonymous().principal( "guest" ).authorities( "ROLE_GUEST" )

.and().rememberMe()

// 기본적으로 사용자 이름 , 암호 , remember-me 만료 시간 및 개인 키를 토큰으로 인코딩하고 쿠키로 사용자의 브라우저에 저장한다 .

// 다음에 사용자가 동일한 웹 애플리케이션에 액세스하면 이 토큰이 감지돼 사용자가 잗종으로 로그인할 수 있따 .

// 정적 Remeber-Me 토큰은 해커가 캡쳐할 수 있기 때문에 보안문제가 있다 . 그러므로 롤링토큰을 지원하지만 ,

// 토큰을 유지하려면 데이터베이스가 필요하다 . 자세한것은 다음게시물에 작성예정이다 .

.and().formLogin().loginPage( "/login.html" ).defaultSuccessUrl( "/books.html" ).failureUrl( "/login.html?error=true" ).permitAll()

// 폼기반 로그인

.and().authorizeRequests().mvcMatchers( "/" , "/h2-console/**" ).permitAll().anyRequest().authenticated() ;

// "/" 들어오는 요청은 모두허용 && 어떤요청이든 인증확인

}

@Override

protected void configure (AuthenticationManagerBuilder auth) throws Exception {

auth.jdbcAuthentication()

.dataSource( dataSource )

.rolePrefix( "ROLE_" )

.usersByUsernameQuery( "SELECT USERNAME, PASSWORD, 'TRUE' as enabled FROM USERS WHERE USERNAME = ?" )

.authoritiesByUsernameQuery( "SELECT A.USERNAME AS USERNAME, B.AUTHORITY AS AUTHORITIES " +

"FROM USERS as A INNER JOIN AUTHORITIES as B " +

"ON A.USERNAME = B.USERNAME " +

"WHERE A.USERNAME = ?" ) ;

}

}

주석처리된것은 기본값이다.

#spring.datasource.url=jdbc:h2:mem:testdb

#spring.datasource.driverClassName=org.h2.Driver

#spring.datasource.username=sa

#spring.datasource.password=

spring.h2.console.enabled = true

https://localhost:8443/h2-console 에 들어가서 아이디와 비번을 성공할시

결과)

바뀐부분)

INSERT INTO USERS (USERNAME, PASSWORD,ENABLED) VALUES

('admin@books.io', '{bcrypt}$2a$10$48zF8yixzhFXLaKQKOFwjO3l8I.4li2yF3a4GPCR2R8adOTXrDDQW',true),

('marten@books.io', '{bcrypt}$2a$10$qIoqRufr5O08a12DuroPWOoDGjVnJx5DbH9c2J8hjnehAfujYNr9y',true),

('jdoe@books.net', '{bcrypt}$2a$10$B8oFxwSvHEYGiqaaIr8AYuVPSorzaf0/cTqwOu7j0kVvqbYIXyasS',false);

-- {noop}은 저장된 암호에 암호화가 적용되지 않았음을 나타낸다.

-- 스프링 보안은 위임을 사용해 사용할 인코딩 방법을 결정한다.

-- 값은 {bcrypt}, {scrypt}, {pdkdf2}, {sha256}이 될수 있다.

-- {sha256}은 주로 호한성을 이유로 존재하며 비보안으로 간주해야 한다.

@Override

protected void configure (AuthenticationManagerBuilder auth) throws Exception {

auth.jdbcAuthentication()

.dataSource( dataSource )

.rolePrefix( "ROLE_" )

.usersByUsernameQuery( "SELECT USERNAME, replace(PASSWORD, '$2y', '$2a') AS PASSWORD, 'TRUE' as enabled FROM USERS WHERE USERNAME = ?" )

.authoritiesByUsernameQuery( "SELECT A.USERNAME AS USERNAME, B.AUTHORITY AS AUTHORITIES " +

"FROM USERS as A INNER JOIN AUTHORITIES as B " +

"ON A.USERNAME = B.USERNAME " +

"WHERE A.USERNAME = ?" ) ;

}

결과)

성공

(이미지 같으므로 생략)

from http://kururu.tistory.com/204 by ccl(A)

댓글

이 블로그의 인기 게시물

데이터 바인딩 추상화 - propertyEditor

[샤니마스 SPRING PARTY2020] THE IDOLM @ STER SHINY COLORS SPRING...

Spring Web Form