Spring Boot를 이용하여 security를 적용하는 방법을 연습해 봅니다.

먼저 프로젝트를 생성해야 테스트를 진행 하겠죠.


spring boot security


상단 메뉴에서 File > New > Spring Starter Project를 이용하여 프로젝트를 생성해도 됩니다.


spring boot security


프로젝트 정보는 테스트용이라 대충 적었습니다.


spring boot security


필요한 라이브러리는 Spring Security만 설명할거기 때문에 복잡하지 않도록 Spring SecuritySpring Web만 선택 했습니다그리고 Finish 버튼을 클릭 하면 자동으로 필요한 라이브러리를 추가하고 기본 소스를 생성 합니다.

 

자동으로 생성된 프로젝트를 실행 해 볼까요?


spring boot security


Spring Boot App을 선택해서 실행을 합니다.


spring boot security


로그인 화면이 나오는데 IDsecurity에서 제공하는 초기값이 user 이고 비밀 번호는 Console 창에 출력을 해줍니다.


spring boot security


이렇게 console 창에 비밀번호가 나타납니다.  복사해서 비밀번호 입력을 하고  Sign in 버튼을 클릭하면 로그인이 진행 됩니다.


spring boot security


오류 메시지가 나오는군요그럼 로그인 성공 입니다.

가야할 페이지가 없어서 나오는 오류 입니다.  프로젝트만 생성하고 화면 작업을 하나도 안했죠.


그럼 갈곳을 정해주고 다시 해봅니다.

Controller 파일을 하나 생성하고


package com.copycoding.security;


import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;


@RestController

public class TestController {

@RequestMapping("/")

    public @ResponseBody String home() throws Exception {

return "Spring Boot";

}



@RestController 어노테이션을 사용합니다화면 만들기 귀찮아 텍스트를 바로 화면으로 출력하도록 했습니다. 홈페이지에 가려고 하는 경우 Spring Boot를 출력하도록 했습니다. 이러면 로그인 하고 여기로 오겠죠.

다시 프로젝트를 실행하고 로그인을 해봅니다.


spring boot security


로그인 성공입니다.

 

이번에는 페이지를 여러개 만들어서 테스트 합니다.

 

package com.copycoding.security;

 

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

 

@RestController

public class TestController {

       

        @RequestMapping("/")

    public @ResponseBody String home() throws Exception {

 

               return "Spring Boot ";

        }

       

        @RequestMapping("/page1")

    public @ResponseBody String pageNo1() throws Exception {

              

               return "Spring Boot : Page No 1";

        }

       

        @RequestMapping("/page2")

    public @ResponseBody String pageNo2() throws Exception {

              

               return "Spring Boot : Page No 2";

        }

}

 

프로젝트를 실행해서 로그인 하고 각 페이지에 접근해 봅니다.


spring boot security


spring boot security


또 성공입니다로그인만 되면 아무 페이지나 막 갈 수 있습니다이러려고 Spring Security를 사용하는게 아니죠?


이번엔 갈 수 있는 페이지와 없는 페이지를 한정해 봅니다그러려면 class 파일을 하나 만들어야 합니다파일에 Security Config를 설정해서 테스트를 진행 합니다.

클래스를 하나 생성 하는데 상속을 받아서 생성을 해야 합니다.


spring boot security


바로 WebSecurityConfigurerAdapter를 찾아서 상속 관계를 만들어 줍니다생성하는 파일명은 편하게 만들면 되고 파일이 만들어 지면 상속된 클래스로 부터 Method 리스트를 찾아 추가해야 합니다.


spring boot security


Method 중에 configure(HttpSecurity http)를 선택하여 추가해 줍니다.  찾기 귀찮으면 그냥 직접 입력 해서 사용 합니다.


 package com.copycoding.security;


import org.springframework.context.annotation.Configuration;

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;


@Configuration

@EnableWebSecurity

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

        .antMatchers("/").permitAll()

        .antMatchers("/page2").hasRole("ADMIN")

        .antMatchers("/page1").hasRole("USER")

        .anyRequest().authenticated();

}

}


Method가 추가 되면 페이지별 권한을 설정 합니다.

“/”는 로그인 하면 접근이 가능하고 다른 페이지 “page1”, “page2”는 아래와 같이 접속 하려면 오류가 발생 합니다.


spring boot security


잠시 Postman으로 설명을 하면


spring boot security


“/”는 접근이 가능 하지만


spring boot security


이렇게 “/page1”, “/page2”ADMIN USER 권한이 있어야 접속이 가능하므로 “Access Denied” 오류가 발생 합니다.

 

이제 ADMIN USER 권한이 있는 로그인 정보를 source에 추가해서 테스트 해보겠습니다.


 package com.copycoding.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.crypto.bcrypt.BCryptPasswordEncoder;

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


@Configuration

@EnableWebSecurity

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


@Override

protected void configure(HttpSecurity http) throws Exception {

http.httpBasic().and().authorizeRequests()

        .antMatchers("/").permitAll()

        .antMatchers("/page2").hasRole("ADMIN")

        .antMatchers("/page1").hasRole("USER")

        .anyRequest().authenticated()

        .and().logout().permitAll()

        .and().formLogin()

        .and().csrf().disable();

}

@Autowired

public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

auth.inMemoryAuthentication()

.withUser("copycoding").password(passwordEncoder().encode("copycopy")).roles("ADMIN");

auth.inMemoryAuthentication()

.withUser("honggil").password(passwordEncoder().encode("hoho")).roles("USER");

}

@Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }

}


“/”는 아무나 접근 가능 합니다.

“/page2”“ADMIN” 권한이 있어야 하는데 아래에 id/pw“copycoding”, ”copycopy” 이고

“/page1” “USER” 권한으로 로그인 정보가 “honggil”, “hoho” 이면 된다고 했습니다.

Postman으로 id password를 전송해서 테스트를 해 봅니다.


spring boot security


ADMIN 권한으로 로그인이 되어서 “/page2”에 잘 접속이 됩니다.


spring boot security


ADMIN 권한으로는 “/page1” 접근은 어림 없군요.

 

logout을 하고 honggil로 로그인 하면 반대의 결과가 나옵니다.

결과는 생략 하겠습니다.

 

logout“.and().logout().permitAll()” 이렇게 설정을 해놓았으므로 사용이 가능하고 사용하는 방법은 다음처럼 http://localhost:9090/logout 을 입력 하면 됩니다.


spring boot security


회원 로그인과 권한을 메모리에 넣고 사용하는 방법으로 테스트를 했는데 이걸 Database에 넣어서 관리하면 되겠네요.  그건 다음에 시간이 되면 테스트 해보겠습니다.


- copy coding -


Spring Boot를 이용하여 Oracle에 연결 하고 간단한 쿼리를 실행하는 예제 입니다.  일반 Spring 작업보다 얼마나 간소화가 되었는지 확인 하기 위해 예제를 만들어 봅니다.

 

- 작업 환경

Spring Tool Suite 4 Version: 4.5.1.RELEASE

JAVA 1.8

Oracle 11g XE


일단 Spring Boot 용 프로젝트를 생성 합니다.


spring boot mybatis oracle


상단 메뉴에서 File > New > Spring Starter Project를 이용해서 프로젝트를 생성해도 됩니다.


spring boot mybatis oracle


기본 값을 그대로 사용하고 Next 버튼을 클릭 하거나 원하는 부분을 수정 입력 합니다.


spring boot mybatis oracle


프로젝트에 포함할 라이브러리를 선택 합니다여기서는 MyBatis, Oracle, Web 만 추가하는 것으로 하고 Finish 버튼을 클릭 하면 프로젝트가 생성 됩니다.

 

pom.xml은 손대지 않고 생성한 그대로 사용 합니다.

 

application.properties에 필요한 설정을 추가합니다.


 #datasource (oracle)

spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver

spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521/xe

spring.datasource.username=copycoding

spring.datasource.password=copycopy

 

#vo location

mybatis.type-aliases-package=com.copycoding.demo.vo

 

#xml location

mybatis.mapper-locations=classpath:mappers/**/*.xml

 

server.port=9090


oracle 관련 정보와 VO 파일이 있는 위치 그리고 쿼리용 xml이 있는 위치를 설정했습니다많은 작업들을 properties에 선언 하는 것으로 해결 됩니다.

 

만들어볼 프로젝트에 필요한 package 와 폴더 구조 그리고 파일의 최종 모습 입니다.


spring boot mybatis oracle


이제 필요한 폴더와 파일을 하나씩 추가 해봅니다.

 

vo 패키지 생성

 

com/copycoding/demo에서 마우스 우측 버튼을 클릭 하고 package를 선택 합니다.


spring boot mybatis oracle


vo 패키지를 하나 생성 하고


spring boot mybatis oracle


테스트용으로 vo 파일을 하나 생성 합니다.

 

EnterpriseMemberVo.java

 package com.copycoding.demo.vo;

 

public class EnterpriseMemberVo {

        private String entrprsMberId;

        private String entrprsSeCode;

        private String bizrno;

        private String jurirno;

        private String cmpnyNm;

        private String cxfc;

        private String zip;

        private String adres;

getter/setter는 생략



mapper 폴더 생성

 

src/main/resources folder 추가 합니다.


spring boot mybatis oracle


폴더 명은 mappers/login으로 입력 했습니다이런 로그인을 구현할건 아닌데...


spring boot mybatis oracle


생성한 mappers 폴더에 EnterpriseMemberVo를 이용한 쿼리를 하나 생성 합니다.  DB를 뒤지다 그냥 걸린 테이블 입니다.


spring boot mybatis oracle


생성한 mappers 폴더에 loginSqlmap.xml으로 파일을 생성 합니다.  파일 내용도 로그인과는 거리가 먼데 그냥 명칭을 맞추기 위해서 적어 봤습니다.

 

쿼리는 EnterpriseMemberVo(아래 부분에서 생성합니다)를 이용한 단순 조회용 이고 DB를 뒤지다 그냥 걸린 테이블 입니다.

 

loginSqlmap.xml

 <?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 namespace="com.copycoding.demo.dao.LoginDao">

   <select id="selectListEnterpriseMember" parameterType="EnterpriseMemberVo"  resultType="EnterpriseMemberVo">

             /* selectListEnterpriseMember */

             SELECT

                    ENTRPRS_MBER_ID as entrprsMberId

                    ,ENTRPRS_SE_CODE as entrprsSeCode

                    ,BIZRNO as bizrno

                    ,JURIRNO as jurirno

                    ,CMPNY_NM as cmpnyNm

                    ,ZIP as zip

                    ,ADRES as adres

             FROM

                    COMTNENTRPRSMBER

            

       </select>

</mapper>

mapper를 사용해서 Dao interface만 작성해도 사용 가능하도록 합니다.

 


java program 생성

 

이제 중요한 부분은 끝났고 나머지 필요한 java 파일만 평소대로 만들면 되겠군요.

controller, service, dao 패키지를 생성 하고 파일을 만들어 봅니다처음에 보여드린 것 처럼 저는 아래처럼 생성 했는데 그냥 한곳에 만들어도 되고 편한대로 패키지를 생성하면 됩니다.


spring boot mybatis oracle


단순 조회라 내용도 별로 없습니다.

controller를 아무 이름으로나 작성 합니다.


 package com.copycoding.demo.controller;

 

import java.util.List;

 

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

import org.springframework.ui.ModelMap;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

 

import com.copycoding.demo.service.LoginService;

import com.copycoding.demo.vo.EnterpriseMemberVo;

 

@RestController

public class TestController {

       

        @Autowired

        LoginService loginService;

       

        @RequestMapping("/member/")

    public @ResponseBody List<EnterpriseMemberVo> selectListEnterpriseMember(

                @ModelAttribute("searchVO") EnterpriseMemberVo enterpriseMemberVo, ModelMap medel) throws Exception {

               List<EnterpriseMemberVo> eList = loginService.selectListEnterpriseMember(enterpriseMemberVo);

              

               return eList;

        }

}


입력 받는게 없지만 그래도 구색은 맞추어서 작업을 해 보았습니다.

RestController로 작업해서 화면을 만들 필요 없이 결과를 그냥 리턴 해도 결과를 확인할 수 있습니다.

 

Service 파일과 Imple도 작성 합니다.

 package com.copycoding.demo.service;

 

import java.util.List;

 

import com.copycoding.demo.vo.EnterpriseMemberVo;

 

public interface LoginService {

        List<EnterpriseMemberVo> selectListEnterpriseMember(EnterpriseMemberVo vo);

}


ServiceImpl

 package com.copycoding.demo.service;

 

import java.util.List;

 

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

import org.springframework.stereotype.Service;

 

import com.copycoding.demo.dao.LoginDao;

import com.copycoding.demo.vo.EnterpriseMemberVo;

 

@Service

public class LoginServiceImpl implements LoginService {

       

        @Autowired

        LoginDao loginDao;

 

        @Override

        public List<EnterpriseMemberVo> selectListEnterpriseMember(EnterpriseMemberVo vo) {

               // TODO Auto-generated method stub

               return loginDao.selectListEnterpriseMember(vo);

        }

 

}


Dao(Mapper)도 위에서 작성한 쿼리용 xml과 철자가 틀리지 않도록 작성해 줍니다.

 package com.copycoding.demo.dao;

 

import java.util.List;

 

import org.apache.ibatis.annotations.Mapper;

 

import com.copycoding.demo.vo.EnterpriseMemberVo;

 

@Mapper

public interface LoginDao {

       

        public List<EnterpriseMemberVo> selectListEnterpriseMember(EnterpriseMemberVo enterpriseMemberVo);

 

}


selectListEnterpriseMember() 함수 이름을 xml에서는 select id 사용을 합니다.

 

이렇게 해서 모든 작업이 완료 되었습니다테스트 할 일만 남았습니다.

 

결과

http://localhost:9090/member/


spring boot mybatis oracle


이상 없이 데이터를 잘 읽어와서 보여주고 있습니다.


- copy coding -



이클립스로 설정 작업을 하려는데 주석이 모두 \uxxxx 형태로 변경되어 읽을 수 없게 되는 경우가 발생하는 경우가 있습니다.  Eclipse Marketplace에 가서 Properties Editor 플러그인을 설치하면 정상으로 돌아온다고 하는데 이것저것 설치하기가 싫은 경우 간단히 설정을 해주기만 하면 다시 한글로 읽을 수 있습니다.

한글이 아래처럼 읽기 어려운 경우


eclipse properties 한글 깨짐


 #-----------------------------------------------------------------------

#

#   globals.properties : \uc2dc\uc2a4\ud15c

#

#-----------------------------------------------------------------------

#   1.  key = value \uad6c\uc870\uc785\ub2c8\ub2e4.

#   2.  key\uac12\uc740 \uacf5\ubc31\ubb38\uc790\ub97c \ud3ec\ud568\ubd88\uac00, value\uac12\uc740 \uacf5\ubc31\ubb38\uc790\ub97c \uac00\ub2a5

#   3.  key\uac12\uc73c\ub85c \ud55c\uae00\uc744 \uc0ac\uc6a9\ubd88\uac00,   value\uac12\uc740 \ud55c\uae00\uc0ac\uc6a9\uc774 \uac00\ub2a5

#   4.  \uc904\uc744 \ubc14\uafc0 \ud544\uc694\uac00 \uc788\uc73c\uba74 '\'\ub97c \ub77c\uc778\uc758 \ub05d\uc5d0 \ucd94\uac00(\ub9cc\uc57d  '\'\ubb38\uc790\ub97c \uc0ac\uc6a9\ud574\uc57c \ud558\ub294 \uacbd\uc6b0\ub294 '\\'\ub97c \uc0ac\uc6a9)

#   5.  Windows\uc5d0\uc11c\uc758 \ub514\ub809\ud1a0\ub9ac \ud45c\uc2dc : '\\' or '/'  ('\' \uc0ac\uc6a9\ud558\uba74 \uc548\ub428)

#   6.  Unix\uc5d0\uc11c\uc758 \ub514\ub809\ud1a0\ub9ac \ud45c\uc2dc : '/'

#   7.  \uc8fc\uc11d\ubb38 \ucc98\ub9ac\ub294  #\uc0ac\uc6a9

#   8.  value\uac12 \ub4a4\uc5d0 \uc2a4\ud398\uc774\uc2a4\uac00 \uc874\uc7ac\ud558\ub294 \uacbd\uc6b0 \uc11c\ube14\ub9bf\uc5d0\uc11c \ucc38\uc870\ud560\ub54c\ub294 \uc5d0\ub7ec\ubc1c\uc0dd\ud560 \uc218 \uc788\uc73c\ubbc0\ub85c trim()\ud558\uac70\ub098 \ub9c8\uc9c0\ub9c9 \uacf5\ubc31\uc5c6\uc774 properties \uac12\uc744 \uc124\uc815\ud560\uac83

#-----------------------------------------------------------------------

 

# \uc6b4\uc601\uc11c\ubc84 \ud0c0\uc785(WINDOWS, UNIX)

Globals.OsType =WINDOWS

 


상단 메뉴에서

Window > Preferences 를 선택 합니다.


eclipse properties 한글 깨짐


Preferences 창이 나오면 좌측에서 General을 확장하고 Content Types를 선택 합니다.


eclipse properties 한글 깨짐


그리고 우측에 보이는 Text를 확장해 주고


eclipse properties 한글 깨짐


Java Properties File을 선택 합니다.

하단의 Default encoding 입력란에 ISO-8859-1 등이 입력되어 있다면 UTF-8을 입력하고 Apply and Close 버튼을 클릭 합니다? Properties 파일이 아직도 깨져 보인다고요파일을 닫고 다시 열어 봅니다.(eclipse를 재시작하지 않아도 됩니다.)


eclipse properties 한글 깨짐


 #-----------------------------------------------------------------------

#

#   globals.properties : 시스템

#

#-----------------------------------------------------------------------

#   1.  key = value 구조입니다.

#   2.  key값은 공백문자를 포함불가, value값은 공백문자를 가능

#   3.  key값으로 한글을 사용불가,   value값은 한글사용이 가능

#   4.  줄을 바꿀 필요가 있으면 '\'를 라인의 끝에 추가(만약  '\'문자를 사용해야 하는 경우는 '\\'를 사용)

#   5.  Windows에서의 디렉토리 표시 : '\\' or '/'  ('\' 사용하면 안됨)

#   6.  Unix에서의 디렉토리 표시 : '/'

#   7.  주석문 처리는  #사용

#   8.  value값 뒤에 스페이스가 존재하는 경우 서블릿에서 참조할때는 에러발생할 수 있으므로 trim()하거나 마지막 공백없이 properties 값을 설정할것

#-----------------------------------------------------------------------

 

# 운영서버 타입(WINDOWS, UNIX)

Globals.OsType =WINDOWS


한글로 잘 보입니다.


- copy coding -


Eclipse에서 Oracle 데이터베이스 연결을 테스트 하기 위해 Data Source Explorer를 이용하는 방법에 대해 알아 봅니다프로젝트에서 환경 설정 값을 이용하여 테스트 하는 방법도 있지만 DB 접속을 위한 프로그램을 완성 하고 나서 디버깅을 해야만 데이터베이스에 접속이 되는지 아닌지 확인할 수 있지만 Data Source Explorer를 사용하면 설정 값만 사용하여 접속이 잘 되었는지 확인이 가능하고 테스트한 설정 값을 Project에 그대로 적용하기만 하면 별도로 접속 테스트 없이 DB 작업이 가능합니다.

 

먼저 이클립스 네비게이터 영역에 Data Source Explorer를 포함해 봅니다.

Window > Show View > Data Source Explorer

메뉴를 선택 합니다.



Eclipse Data Source Explorer Oracle


만약 Show View 메뉴에서 Data Source Explorer가 보이지 않는다면 하단의 Other...를 선택하여 팝업 메뉴 창을 오픈 합니다.


Eclipse Data Source Explorer Oracle


이곳에서 Data Source Explorer를 찾아 클릭 하면 됩니다.


Eclipse Data Source Explorer Oracle


그러면 좌측 네비게이터 창에 탭으로 추가된 것을 볼 수 있습니다이제 Database를 추가하기 위해 Database Connections에 마우스를 대고 우측 버튼 클릭을 합니다.


Eclipse Data Source Explorer Oracle


팝업 메뉴에서 New...를 선택해 줍니다.


Eclipse Data Source Explorer Oracle


Oracle을 사용하려고 하기 때문에 Oracle을 선택 하고 Name은 임의대로 입력 해도 됩니다이름을 입력 했다면 Next 버튼을 클릭 합니다.


Eclipse Data Source Explorer Oracle


아직 연결을 위한 드라이버가 하나도 생성되어 있지 않아 콤보박스에 선택을 할 수 없고 Drivers 입력란 우측에 있는 원안의 별표를 클릭 합니다.


Eclipse Data Source Explorer Oracle


Name/Type 탭에서 사용하려는 오라클 버전을 선택 합니다저는 사용하려는 버전이 11g이므로11을 선택 했습니다다음으로 JAR List 탭을 선택 합니다.


Eclipse Data Source Explorer Oracle


기본값이 설정되어 있지만 자신이 설치한 오라클에 적합한 jar 파일을 선택해야 합니다. 저는 11g XE 버전을 사용 중 이고 Add JAR/Zip... 버튼을 클릭해서 설치한 oracle 폴더로 이동 해보면

 

C:\oraclexe\app\oracle\product\11.2.0\server\jdbc\lib

 

이 폴더에 드라이버 파일이 있습니다


Eclipse Data Source Explorer Oracle


여기서는 ojdbc6.jar를 선택 합니다.  그리고 기존에 있던 ojdbc14.jar는 Remove JAR/Zip 버튼을 클릭하여 리스트에서 삭제처리 합니다.


Eclipse Data Source Explorer Oracle


이제 OK 버튼이 활성화 되어 클릭 할 수 있습니다. 클릭 하면 profile 입력 창이 보입니다.


Eclipse Data Source Explorer Oracle


database에 접속을 위한 정보를 입력하고 Test Connection 버튼을 클릭하여 접속 테스트를 진행 합니다.


Eclipse Data Source Explorer Oracle


설정 값에 이상이 없고 연결도 잘 되었습니다. Finish 버튼을 클릭하여 연결 테스트를 종료 합니다만일 설정된 정보를 확인하려면 Next 버튼을 클릭하면 됩니다.


Eclipse Data Source Explorer Oracle


현재 설정된 상세 정보를 볼 수 있고 수정이 필요하면 Back 버튼으로 수정을 하고 테스트를 종료 하려면 Finish 버튼을 클릭 합니다.


Eclipse Data Source Explorer Oracle


데이터베이스 접속 테스트가 성공 하였으니 접속 정보를 위와 같이 프로젝트 설정에 사용하면 DB 접속은 오류 없이 진행 됩니다.


- copy coding -


12345678

+ Recent posts