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)
댓글
댓글 쓰기